Merge "Add debug logs to debug flaky workspace binding" into tm-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 4fbe8cf..e8f2496 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.anim.AlphaUpdateListener;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.keyboard.FocusIndicatorHelper;
 import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
 import com.android.launcher3.model.data.ItemInfo;
@@ -65,7 +64,6 @@
     private FloatingHeaderView mParent;
 
     private boolean mPredictionsEnabled = false;
-    private @Nullable List<ItemInfo> mPendingPredictedItems;
     private OnLongClickListener mOnIconLongClickListener = ItemLongClickListener.INSTANCE_ALL_APPS;
 
     public PredictionRowView(@NonNull Context context) {
@@ -159,18 +157,10 @@
      * we can optimize by swapping them in place.
      */
     public void setPredictedApps(List<ItemInfo> items) {
-        if (!FeatureFlags.ENABLE_APP_PREDICTIONS_WHILE_VISIBLE.get()
-                && !mActivityContext.isBindingItems()
-                && isShown()
-                && getWindowVisibility() == View.VISIBLE) {
-            mPendingPredictedItems = items;
-            return;
-        }
         applyPredictedApps(items);
     }
 
     private void applyPredictedApps(List<ItemInfo> items) {
-        mPendingPredictedItems = null;
         mPredictedApps.clear();
         mPredictedApps.addAll(items.stream()
                 .filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo)
@@ -265,13 +255,4 @@
         return getChildAt(0);
     }
 
-
-    @Override
-    public void onVisibilityAggregated(boolean isVisible) {
-        super.onVisibilityAggregated(isVisible);
-
-        if (mPendingPredictedItems != null && !isVisible) {
-            applyPredictedApps(mPendingPredictedItems);
-        }
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 0a2a9b3..5c2f6b1 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -41,7 +41,6 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.graphics.DragPreviewProvider;
@@ -279,32 +278,6 @@
      * Sets or updates the predicted items
      */
     public void setPredictedItems(FixedContainerItems items) {
-        boolean shouldIgnoreVisibility = FeatureFlags.ENABLE_APP_PREDICTIONS_WHILE_VISIBLE.get()
-                || mLauncher.isWorkspaceLoading()
-                || mPredictedItems.equals(items.items)
-                || mHotseat.getShortcutsAndWidgets().getChildCount() < mHotSeatItemsCount;
-        if (!shouldIgnoreVisibility
-                && mHotseat.isShown()
-                && mHotseat.getWindowVisibility() == View.VISIBLE) {
-            mHotseat.setOnVisibilityAggregatedCallback((isVisible) -> {
-                if (isVisible) {
-                    return;
-                }
-                mHotseat.setOnVisibilityAggregatedCallback(null);
-
-                applyPredictedItems(items);
-            });
-        } else {
-            mHotseat.setOnVisibilityAggregatedCallback(null);
-
-            applyPredictedItems(items);
-        }
-    }
-
-    /**
-     * Sets or updates the predicted items only once the hotseat becomes hidden to the user
-     */
-    private void applyPredictedItems(FixedContainerItems items) {
         mPredictedItems = new ArrayList(items.items);
         if (mPredictedItems.isEmpty()) {
             HotseatRestoreHelper.restoreBackup(mLauncher);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 3dbe6c8..951a9b6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -35,7 +35,6 @@
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_WIDGET_PICKER_DEPTH;
 import static com.android.launcher3.config.FeatureFlags.RECEIVE_UNFOLD_EVENTS_FROM_SYSUI;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
@@ -169,7 +168,6 @@
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.unfold.RemoteUnfoldSharedComponent;
 import com.android.systemui.unfold.UnfoldSharedComponent;
@@ -187,7 +185,6 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
@@ -235,7 +232,8 @@
         RecentsView overviewPanel = getOverviewPanel();
         mSplitSelectStateController =
                 new SplitSelectStateController(this, mHandler, getStateManager(),
-                        getDepthController(), getStatsLogManager());
+                        getDepthController(), getStatsLogManager(),
+                        SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this));
         overviewPanel.init(mActionsView, mSplitSelectStateController);
         mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
                 mSplitSelectStateController);
@@ -253,8 +251,7 @@
         mDesktopVisibilityController = new DesktopVisibilityController(this);
         mHotseatPredictionController = new HotseatPredictionController(this);
 
-        mEnableWidgetDepth = ENABLE_WIDGET_PICKER_DEPTH.get()
-                && SystemProperties.getBoolean("ro.launcher.depth.widget", true);
+        mEnableWidgetDepth = SystemProperties.getBoolean("ro.launcher.depth.widget", true);
         getWorkspace().addOverlayCallback(progress ->
                 onTaskbarInAppDisplayProgressUpdate(progress, MINUS_ONE_PAGE_PROGRESS_INDEX));
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index f26189c..3f8da56 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -134,7 +134,8 @@
 
         SplitSelectStateController controller =
                 new SplitSelectStateController(this, mHandler, getStateManager(),
-                        /* depthController */ null, getStatsLogManager());
+                         null /* depthController */, getStatsLogManager(),
+                        SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this));
         mDragLayer.recreateControllers();
         mFallbackRecentsView.init(mActionsView, controller);
 
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index e58845a..255b910 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -18,7 +18,6 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
 
 import android.app.Activity;
@@ -393,10 +392,7 @@
                     taskContainer.getThumbnailView().getTaskOverlay()
                             .getModalStateSystemShortcut(
                                     taskContainer.getItemInfo(), taskContainer.getTaskView());
-            if (ENABLE_OVERVIEW_SELECTIONS.get()) {
-                return createSingletonShortcutList(modalStateSystemShortcut);
-            }
-            return null;
+            return createSingletonShortcutList(modalStateSystemShortcut);
         }
     };
 }
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 51afb5b..d1100c7 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -107,14 +107,15 @@
     private FloatingTaskView mFirstFloatingTaskView;
 
     public SplitSelectStateController(Context context, Handler handler, StateManager stateManager,
-            DepthController depthController, StatsLogManager statsLogManager) {
+            DepthController depthController, StatsLogManager statsLogManager,
+            SystemUiProxy systemUiProxy, RecentsModel recentsModel) {
         mContext = context;
         mHandler = handler;
         mStatsLogManager = statsLogManager;
-        mSystemUiProxy = SystemUiProxy.INSTANCE.get(mContext);
+        mSystemUiProxy = systemUiProxy;
         mStateManager = stateManager;
         mDepthController = depthController;
-        mRecentTasksModel = RecentsModel.INSTANCE.get(context);
+        mRecentTasksModel = recentsModel;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index f578b87..de7e2f0 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -56,7 +56,8 @@
             HIDDEN_NO_RECENTS,
             HIDDEN_SPLIT_SCREEN,
             HIDDEN_SPLIT_SELECT_ACTIVE,
-            HIDDEN_ACTIONS_IN_MENU
+            HIDDEN_ACTIONS_IN_MENU,
+            HIDDEN_DESKTOP
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ActionsHiddenFlags { }
@@ -67,6 +68,7 @@
     public static final int HIDDEN_SPLIT_SCREEN = 1 << 3;
     public static final int HIDDEN_SPLIT_SELECT_ACTIVE = 1 << 4;
     public static final int HIDDEN_ACTIONS_IN_MENU = 1 << 5;
+    public static final int HIDDEN_DESKTOP = 1 << 6;
 
     @IntDef(flag = true, value = {
             DISABLED_SCROLLING,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index e529b04..56e85c6 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -62,6 +62,7 @@
 import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET;
 import static com.android.quickstep.views.OverviewActionsView.FLAG_SINGLE_TASK;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_ACTIONS_IN_MENU;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_DESKTOP;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
@@ -3767,6 +3768,10 @@
                 !mActivity.getDeviceProfile().isTablet);
         mActionsView.updateSplitButtonDisabledFlags(FLAG_SINGLE_TASK,
                 !FeatureFlags.ENABLE_TASKBAR_IN_OVERVIEW.get() && getTaskViewCount() <= 1);
+        if (DESKTOP_MODE_SUPPORTED) {
+            boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
+            mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index 0da70a9..428bd95 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -75,6 +75,7 @@
         clipToOutline = true
 
         shouldScaleArrow = true
+        mIsArrowRotated = true
         // This synchronizes the arrow and menu to open at the same time
         OPEN_CHILD_FADE_START_DELAY = OPEN_FADE_START_DELAY
         OPEN_CHILD_FADE_DURATION = OPEN_FADE_DURATION
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
index aed26d3..735c5e6 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
@@ -92,6 +92,7 @@
     @TaskbarModeSwitch(mode = TRANSIENT)
     public void testTransientLaunchApp() throws Exception {
         getTaskbar().getAppIcon(TEST_APP_NAME).launch(TEST_APP_PACKAGE);
+        mLauncher.getLaunchedAppState().assertTaskbarHidden();
     }
 
     @Test
@@ -140,6 +141,7 @@
     public void testTransientLaunchAppInSplitscreen() throws Exception {
         getTaskbar().getAppIcon(TEST_APP_NAME).dragToSplitscreen(
                 TEST_APP_PACKAGE, CALCULATOR_APP_PACKAGE);
+        mLauncher.getLaunchedAppState().assertTaskbarHidden();
     }
 
     @Test
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
new file mode 100644
index 0000000..9db0368
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -0,0 +1,212 @@
+/*
+ *  Copyright (C) 2023 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package com.android.quickstep.util
+
+import android.app.ActivityManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Rect
+import android.os.Handler
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.LauncherState
+import com.android.launcher3.logging.StatsLogManager
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.statehandlers.DepthController
+import com.android.launcher3.statemanager.StateManager
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.withArgCaptor
+import com.android.quickstep.RecentsModel
+import com.android.quickstep.SystemUiProxy
+import com.android.systemui.shared.recents.model.Task
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.ArrayList
+import java.util.function.Consumer
+
+
+@RunWith(AndroidJUnit4::class)
+class SplitSelectStateControllerTest {
+
+    @Mock lateinit var systemUiProxy: SystemUiProxy
+    @Mock lateinit var depthController: DepthController
+    @Mock lateinit var statsLogManager: StatsLogManager
+    @Mock lateinit var stateManager: StateManager<LauncherState>
+    @Mock lateinit var handler: Handler
+    @Mock lateinit var context: Context
+    @Mock lateinit var recentsModel: RecentsModel
+
+    lateinit var splitSelectStateController: SplitSelectStateController
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        splitSelectStateController = SplitSelectStateController(context, handler,
+                stateManager, depthController, statsLogManager, systemUiProxy, recentsModel)
+    }
+
+    @Test
+    fun activeTasks_noMatchingTasks() {
+        val groupTask1 = generateGroupTask(
+                ComponentName("pomegranate", "juice"),
+                ComponentName("pumpkin", "pie"))
+        val groupTask2 = generateGroupTask(
+                ComponentName("hotdog", "juice"),
+                ComponentName("personal", "computer"))
+        val tasks: ArrayList<GroupTask> = ArrayList()
+        tasks.add(groupTask1)
+        tasks.add(groupTask2)
+
+        // Assertions happen in the callback we get from what we pass into
+        // #findLastActiveTaskAndRunCallback
+        val taskConsumer = Consumer<Task> {
+            assertNull("No tasks should have matched", it /*task*/)
+        }
+
+        // Capture callback from recentsModel#getTasks()
+        val consumer = withArgCaptor<Consumer<ArrayList<GroupTask>>> {
+            splitSelectStateController.findLastActiveTaskAndRunCallback(
+                    ComponentName("no", "match"), taskConsumer)
+            verify(recentsModel).getTasks(capture())
+        }
+
+        // Send our mocked tasks
+        consumer.accept(tasks)
+    }
+
+    @Test
+    fun activeTasks_singleMatchingTask() {
+        val matchingPackage = "hotdog"
+        val matchingClass = "juice"
+        val groupTask1 = generateGroupTask(
+                ComponentName(matchingPackage, matchingClass),
+                ComponentName("pomegranate", "juice"))
+        val groupTask2 = generateGroupTask(
+                ComponentName("pumpkin", "pie"),
+                ComponentName("personal", "computer"))
+        val tasks: ArrayList<GroupTask> = ArrayList()
+        tasks.add(groupTask1)
+        tasks.add(groupTask2)
+
+        // Assertions happen in the callback we get from what we pass into
+        // #findLastActiveTaskAndRunCallback
+        val taskConsumer = Consumer<Task> {
+            assertEquals("ComponentName package mismatched",
+                    it.key.baseIntent.component.packageName, matchingPackage)
+            assertEquals("ComponentName class mismatched",
+                    it.key.baseIntent.component.className, matchingClass)
+            assertEquals(it, groupTask1.task1)
+        }
+
+        // Capture callback from recentsModel#getTasks()
+        val consumer = withArgCaptor<Consumer<ArrayList<GroupTask>>> {
+            splitSelectStateController.findLastActiveTaskAndRunCallback(
+                    ComponentName(matchingPackage, matchingClass), taskConsumer)
+            verify(recentsModel).getTasks(capture())
+        }
+
+        // Send our mocked tasks
+        consumer.accept(tasks)
+    }
+
+    @Test
+    fun activeTasks_multipleMatchMostRecentTask() {
+        val matchingPackage = "hotdog"
+        val matchingClass = "juice"
+        val groupTask1 = generateGroupTask(
+                ComponentName(matchingPackage, matchingClass),
+                ComponentName("pumpkin", "pie"))
+        val groupTask2 = generateGroupTask(
+                ComponentName("pomegranate", "juice"),
+                ComponentName(matchingPackage, matchingClass))
+        val tasks: ArrayList<GroupTask> = ArrayList()
+        tasks.add(groupTask2)
+        tasks.add(groupTask1)
+
+        // Assertions happen in the callback we get from what we pass into
+        // #findLastActiveTaskAndRunCallback
+        val taskConsumer = Consumer<Task> {
+            assertEquals("ComponentName package mismatched",
+                    it.key.baseIntent.component.packageName, matchingPackage)
+            assertEquals("ComponentName class mismatched",
+                    it.key.baseIntent.component.className, matchingClass)
+            assertEquals(it, groupTask2.task2)
+        }
+
+        // Capture callback from recentsModel#getTasks()
+        val consumer = withArgCaptor<Consumer<ArrayList<GroupTask>>> {
+            splitSelectStateController.findLastActiveTaskAndRunCallback(
+                    ComponentName(matchingPackage, matchingClass), taskConsumer)
+            verify(recentsModel).getTasks(capture())
+        }
+
+        // Send our mocked tasks
+        consumer.accept(tasks)
+    }
+
+    @Test
+    fun setInitialApp_withTaskId() {
+        splitSelectStateController.setInitialTaskSelect(null /*intent*/,
+                -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, 10 /*alreadyRunningTask*/)
+        assertTrue(splitSelectStateController.isSplitSelectActive)
+    }
+
+    @Test
+    fun setInitialApp_withIntent() {
+        splitSelectStateController.setInitialTaskSelect(Intent() /*intent*/,
+                -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/, -1 /*alreadyRunningTask*/)
+        assertTrue(splitSelectStateController.isSplitSelectActive)
+    }
+
+    @Test
+    fun resetAfterInitial() {
+        splitSelectStateController.setInitialTaskSelect(Intent() /*intent*/,
+                -1 /*stagePosition*/, ItemInfo(), null /*splitEvent*/,
+                -1)
+        splitSelectStateController.resetState()
+        assertFalse(splitSelectStateController.isSplitSelectActive)
+    }
+
+    private fun generateGroupTask(task1ComponentName: ComponentName,
+                                  task2ComponentName: ComponentName): GroupTask {
+        val task1 = Task()
+        var taskInfo = ActivityManager.RunningTaskInfo()
+        var intent = Intent()
+        intent.component = task1ComponentName
+        taskInfo.baseIntent = intent
+        task1.key = Task.TaskKey(taskInfo)
+
+        val task2 = Task()
+        taskInfo = ActivityManager.RunningTaskInfo()
+        intent = Intent()
+        intent.component = task2ComponentName
+        taskInfo.baseIntent = intent
+        task2.key = Task.TaskKey(taskInfo)
+        return GroupTask(task1, task2, SplitConfigurationOptions.SplitBounds(
+                Rect(), Rect(), -1, -1
+        ))
+    }
+}
\ No newline at end of file
diff --git a/res/color/app_subtitle_text_dark.xml b/res/color/app_subtitle_text_dark.xml
new file mode 100644
index 0000000..220d10f
--- /dev/null
+++ b/res/color/app_subtitle_text_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="#EFF1F2" android:state_expanded="false" />
+    <item android:color="#191C1D" android:state_expanded="true" />
+</selector>
diff --git a/res/color/app_subtitle_text_light.xml b/res/color/app_subtitle_text_light.xml
new file mode 100644
index 0000000..fb00baa
--- /dev/null
+++ b/res/color/app_subtitle_text_light.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="?android:attr/textColorSecondary"/>
+</selector>
diff --git a/res/color/app_title_text_dark.xml b/res/color/app_title_text_dark.xml
new file mode 100644
index 0000000..220d10f
--- /dev/null
+++ b/res/color/app_title_text_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="#EFF1F2" android:state_expanded="false" />
+    <item android:color="#191C1D" android:state_expanded="true" />
+</selector>
diff --git a/res/color/app_title_text_light.xml b/res/color/app_title_text_light.xml
new file mode 100644
index 0000000..bb52973
--- /dev/null
+++ b/res/color/app_title_text_light.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="?android:attr/textColorPrimary"/>
+</selector>
diff --git a/res/drawable/bg_widgets_content.xml b/res/drawable/bg_widgets_content.xml
index 8060430..b0b699b 100644
--- a/res/drawable/bg_widgets_content.xml
+++ b/res/drawable/bg_widgets_content.xml
@@ -18,16 +18,32 @@
     <!--
        L -> large radius
        s -> small radius
-       0 -> no radiuls
+       0 -> no radius
     -->
 
+    <!-- SINGLE : L L L L -->
+    <item android:state_single="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/surface" />
+            <corners android:radius="@dimen/widget_list_top_bottom_corner_radius"/>
+        </shape>
+    </item>
+
+    <!-- FIRST : 0 0 s s -->
+    <item android:state_first="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/surface" />
+            <corners
+                android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
+                android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
+        </shape>
+    </item>
+
     <!-- MIDDLE : 0 0 s s -->
     <item android:state_middle="true">
         <shape android:shape="rectangle">
             <solid android:color="@color/surface" />
             <corners
-                android:topLeftRadius="0dp"
-                android:topRightRadius="0dp"
                 android:bottomLeftRadius="@dimen/widget_list_content_corner_radius"
                 android:bottomRightRadius="@dimen/widget_list_content_corner_radius" />
         </shape>
@@ -38,8 +54,6 @@
         <shape android:shape="rectangle">
             <solid android:color="@color/surface" />
             <corners
-                android:topLeftRadius="0dp"
-                android:topRightRadius="0dp"
                 android:bottomLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
                 android:bottomRightRadius="@dimen/widget_list_top_bottom_corner_radius" />
         </shape>
diff --git a/res/drawable/bg_widgets_header_large_screen.xml b/res/drawable/bg_widgets_header_large_screen.xml
new file mode 100644
index 0000000..e1408cc
--- /dev/null
+++ b/res/drawable/bg_widgets_header_large_screen.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    android:insetTop="@dimen/widget_list_entry_spacing" >
+    <ripple
+        android:color="@color/accent_ripple_color"
+        android:paddingTop="@dimen/widget_list_header_view_vertical_padding"
+        android:paddingBottom="@dimen/widget_list_header_view_vertical_padding" >
+        <item android:id="@android:id/mask"
+            android:drawable="@drawable/bg_widgets_header_states_large_screen" />
+        <item android:drawable="@drawable/bg_widgets_header_states_large_screen" />
+    </ripple>
+</inset>
diff --git a/res/drawable/bg_widgets_header_states_large_screen.xml b/res/drawable/bg_widgets_header_states_large_screen.xml
new file mode 100644
index 0000000..1ee5fe5
--- /dev/null
+++ b/res/drawable/bg_widgets_header_states_large_screen.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_expanded="true">
+        <shape android:shape="rectangle">
+            <solid android:color="@color/widget_picker_background_selected" />
+            <corners android:radius="@dimen/widget_list_top_bottom_corner_radius" />
+        </shape>
+    </item>
+
+    <item android:state_expanded="false">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/transparent" />
+            <corners android:radius="@dimen/widget_list_top_bottom_corner_radius" />
+        </shape>
+    </item>
+</selector>
diff --git a/res/layout/widgets_full_sheet_large_screen.xml b/res/layout/widgets_full_sheet_large_screen.xml
index 1c0037d..b99ac5c 100644
--- a/res/layout/widgets_full_sheet_large_screen.xml
+++ b/res/layout/widgets_full_sheet_large_screen.xml
@@ -31,23 +31,12 @@
             android:id="@+id/recycler_view_container"
             android:layout_width="0dp"
             android:layout_height="0dp"
-            app:layout_constraintEnd_toStartOf="@id/right_pane"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintTop_toBottomOf="@id/title"
             app:layout_constraintWidth_percent="0.33">
 
             <TextView
-                android:id="@+id/no_widgets_text"
-                style="@style/PrimaryHeadline"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:gravity="center"
-                android:textSize="18sp"
-                android:visibility="gone"
-                tools:text="No widgets available" />
-
-            <TextView
                 android:id="@+id/fast_scroller_popup"
                 style="@style/FastScrollerPopup"
                 android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
@@ -68,28 +57,42 @@
                 android:visibility="gone" />
         </FrameLayout>
 
-        <ScrollView
-            android:id="@+id/right_pane"
+        <FrameLayout
             android:layout_width="0dp"
             android:layout_height="0dp"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toEndOf="@id/recycler_view_container"
             app:layout_constraintTop_toBottomOf="@id/title"
-            app:layout_constraintBottom_toBottomOf="parent"
             android:paddingEnd="16dp"
             android:paddingStart="8dp"
             android:layout_marginTop="26dp"
-            app:layout_constraintWidth_percent="0.67">
-
-            <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
-                android:id="@+id/recommended_widget_table"
+            app:layout_constraintWidth_percent="0.67"
+            app:layout_constraintBottom_toBottomOf="parent"
+            android:orientation="horizontal">
+            <TextView
+                android:id="@+id/no_widgets_text"
+                style="@style/PrimaryHeadline"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:background="@drawable/widgets_surface_background"
-                android:paddingHorizontal="@dimen/widget_list_horizontal_margin_large_screen"
-                android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
-                android:visibility="gone" />
-        </ScrollView>
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:textSize="18sp"
+                android:visibility="gone"
+                tools:text="No widgets available" />
+            <ScrollView
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:id="@+id/right_pane">
+                    <com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
+                        android:id="@+id/recommended_widget_table"
+                        android:background="@drawable/widgets_surface_background"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:paddingHorizontal=
+                            "@dimen/widget_list_horizontal_margin_large_screen"
+                        android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
+                        android:visibility="gone" />
+            </ScrollView>
+        </FrameLayout>
 
         <View
             android:id="@+id/collapse_handle"
diff --git a/res/layout/widgets_list_row_header_two_pane.xml b/res/layout/widgets_list_row_header_two_pane.xml
new file mode 100644
index 0000000..6465db5
--- /dev/null
+++ b/res/layout/widgets_list_row_header_two_pane.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.launcher3.widget.picker.WidgetsListHeader xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/widgets_list_header"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:importantForAccessibility="yes"
+    android:focusable="true"
+    launcher:appIconSize="48dp"
+    android:descendantFocusability="afterDescendants"
+    android:background="@drawable/bg_widgets_header_large_screen" >
+
+    <ImageView
+        android:id="@+id/app_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="16dp"
+        android:importantForAccessibility="no"
+        tools:src="@drawable/ic_corp"/>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:duplicateParentState="true">
+
+        <TextView
+            android:id="@+id/app_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="start|center_vertical"
+            android:ellipsize="end"
+            android:maxLines="1"
+            android:textColor="?attr/widgetPickerHeaderAppTitleColor"
+            android:textSize="16sp"
+            android:duplicateParentState="true"
+            tools:text="App name" />
+
+        <TextView
+            android:id="@+id/app_subtitle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:maxLines="1"
+            android:textColor="?attr/widgetPickerHeaderAppSubtitleColor"
+            android:alpha="0.7"
+            android:duplicateParentState="true"
+            tools:text="m widgets, n shortcuts" />
+
+    </LinearLayout>
+</com.android.launcher3.widget.picker.WidgetsListHeader>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index c3bd90e..f70937a 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -55,6 +55,8 @@
     <attr name="dropTargetHoverTextColor" format="color" />
     <attr name="preloadIconAccentColor" format="color" />
     <attr name="preloadIconBackgroundColor" format="color" />
+    <attr name="widgetPickerHeaderAppTitleColor" format="color"/>
+    <attr name="widgetPickerHeaderAppSubtitleColor" format="color"/>
 
     <!-- BubbleTextView specific attributes. -->
     <declare-styleable name="BubbleTextView">
diff --git a/res/values/styles.xml b/res/values/styles.xml
index a0b5570..474a289 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -176,10 +176,14 @@
         <item name="android:colorPrimaryDark">#E8EAED</item>
         <item name="android:textColorSecondary">?android:attr/textColorPrimary</item>
         <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
+        <item name="widgetPickerHeaderAppTitleColor">@color/app_title_text_light</item>
+        <item name="widgetPickerHeaderAppSubtitleColor">@color/app_subtitle_text_light</item>
     </style>
     <style name="WidgetContainerTheme.Dark" parent="AppTheme.Dark">
         <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
         <item name="android:colorPrimaryDark">#616161</item> <!-- Gray 700 -->
+        <item name="widgetPickerHeaderAppTitleColor">@color/app_title_text_dark</item>
+        <item name="widgetPickerHeaderAppSubtitleColor">@color/app_subtitle_text_dark</item>
     </style>
 
     <style name="FastScrollerPopup" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index bf492a9..03afba1 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -27,10 +27,6 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
-import androidx.annotation.Nullable;
-
-import java.util.function.Consumer;
-
 /**
  * View class that represents the bottom row of the home screen.
  */
@@ -43,8 +39,6 @@
     private boolean mHasVerticalHotseat;
     private Workspace<?> mWorkspace;
     private boolean mSendTouchToWorkspace;
-    @Nullable
-    private Consumer<Boolean> mOnVisibilityAggregatedCallback;
 
     private final View mQsb;
 
@@ -151,20 +145,6 @@
     }
 
     @Override
-    public void onVisibilityAggregated(boolean isVisible) {
-        super.onVisibilityAggregated(isVisible);
-
-        if (mOnVisibilityAggregatedCallback != null) {
-            mOnVisibilityAggregatedCallback.accept(isVisible);
-        }
-    }
-
-    /** Sets a callback to be called onVisibilityAggregated */
-    public void setOnVisibilityAggregatedCallback(@Nullable Consumer<Boolean> callback) {
-        mOnVisibilityAggregatedCallback = callback;
-    }
-
-    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 604c1b8..4ac7f07 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -16,8 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherPrefs.GRID_NAME;
 import static com.android.launcher3.Utilities.dpiFromPx;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_DEVICE_PROFILE_LOGGING;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TWO_PANEL_HOME;
 import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
@@ -69,8 +69,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -93,8 +91,6 @@
     public static final int TYPE_MULTI_DISPLAY = 1;
     public static final int TYPE_TABLET = 2;
 
-    private static final String KEY_IDP_GRID_NAME = "idp_grid_name";
-
     private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48;
 
     // Constants that affects the interpolation curve between statically defined device profile
@@ -207,8 +203,7 @@
         String gridName = getCurrentGridName(context);
         String newGridName = initGrid(context, gridName);
         if (!newGridName.equals(gridName)) {
-            LauncherPrefs.getPrefs(context).edit().putString(KEY_IDP_GRID_NAME, newGridName)
-                    .apply();
+            LauncherPrefs.get(context).put(GRID_NAME, newGridName);
         }
         new DeviceGridState(this).writeToPrefs(context);
 
@@ -316,7 +311,7 @@
     }
 
     public static String getCurrentGridName(Context context) {
-        return LauncherPrefs.getPrefs(context).getString(KEY_IDP_GRID_NAME, null);
+        return LauncherPrefs.get(context).get(GRID_NAME);
     }
 
     private String initGrid(Context context, String gridName) {
@@ -458,9 +453,8 @@
 
 
     public void setCurrentGrid(Context context, String gridName) {
-        Context appContext = context.getApplicationContext();
-        LauncherPrefs.getPrefs(appContext).edit().putString(KEY_IDP_GRID_NAME, gridName).apply();
-        MAIN_EXECUTOR.execute(() -> onConfigChanged(appContext));
+        LauncherPrefs.get(context).put(GRID_NAME, gridName);
+        MAIN_EXECUTOR.execute(() -> onConfigChanged(context.getApplicationContext()));
     }
 
     private Object[] toModelState() {
@@ -683,18 +677,6 @@
         float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
         int rotation = WindowManagerProxy.INSTANCE.get(context).getRotation(context);
 
-        if (Utilities.IS_DEBUG_DEVICE && ENABLE_DEVICE_PROFILE_LOGGING.get()) {
-            StringWriter stringWriter = new StringWriter();
-            PrintWriter printWriter = new PrintWriter(stringWriter);
-            DisplayController.INSTANCE.get(context).dump(printWriter);
-            printWriter.flush();
-            Log.d("b/253338238", "getDeviceProfile -"
-                            + "\nconfig: " + config
-                            + "\ndisplayMetrics: " + res.getDisplayMetrics()
-                            + "\nrotation: " + rotation
-                            + "\n" + stringWriter,
-                    new Exception());
-        }
         return getBestMatch(screenWidth, screenHeight, rotation);
     }
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7dffd33..de60d05 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -19,7 +19,6 @@
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
 import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
-import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
@@ -86,7 +85,6 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -117,7 +115,6 @@
 import android.view.WindowManager.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
-import android.widget.ImageView;
 import android.widget.Toast;
 
 import androidx.annotation.CallSuper;
@@ -151,7 +148,6 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.logger.LauncherAtom;
@@ -306,8 +302,6 @@
     private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
     @Thunk @VisibleForTesting public static final int NEW_APPS_ANIMATION_DELAY = 500;
 
-    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;
@@ -504,7 +498,6 @@
         mAppWidgetHolder.startListening();
 
         setupViews();
-        crossFadeWithPreviousAppearance();
         mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
 
         boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
@@ -1579,16 +1572,6 @@
     public Object onRetainNonConfigurationInstance() {
         NonConfigInstance instance = new NonConfigInstance();
         instance.config = new Configuration(mOldConfig);
-
-        int width = mDragLayer.getWidth();
-        int height = mDragLayer.getHeight();
-
-        if (FeatureFlags.ENABLE_LAUNCHER_ACTIVITY_THEME_CROSSFADE.get()
-                && width > 0
-                && height > 0) {
-            instance.snapshot =
-                    BitmapRenderer.createHardwareBitmap(width, height, mDragLayer::draw);
-        }
         return instance;
     }
 
@@ -3294,41 +3277,6 @@
         return (T) activityContext;
     }
 
-    /**
-     * Cross-fades the launcher's updated appearance with its previous appearance.
-     *
-     * This method is used to cross-fade UI updates on activity creation, specifically dark mode
-     * updates.
-     */
-    private void crossFadeWithPreviousAppearance() {
-        NonConfigInstance lastInstance = (NonConfigInstance) getLastNonConfigurationInstance();
-
-        if (lastInstance == null || lastInstance.snapshot == null) {
-            return;
-        }
-
-        ImageView crossFadeHelper = new ImageView(this);
-        crossFadeHelper.setImageBitmap(lastInstance.snapshot);
-        crossFadeHelper.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-
-        InsettableFrameLayout.LayoutParams layoutParams = new InsettableFrameLayout.LayoutParams(
-                InsettableFrameLayout.LayoutParams.MATCH_PARENT,
-                InsettableFrameLayout.LayoutParams.MATCH_PARENT);
-
-        layoutParams.ignoreInsets = true;
-
-        crossFadeHelper.setLayoutParams(layoutParams);
-
-        getRootView().addView(crossFadeHelper);
-
-        crossFadeHelper
-                .animate()
-                .setDuration(THEME_CROSS_FADE_ANIMATION_DURATION)
-                .alpha(0f)
-                .withEndAction(() -> getRootView().removeView(crossFadeHelper))
-                .start();
-    }
-
     public boolean supportsAdaptiveIconAnimation(View clickedView) {
         return false;
     }
@@ -3360,7 +3308,6 @@
 
     private static class NonConfigInstance {
         public Configuration config;
-        public Bitmap snapshot;
     }
 
     @Override
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 2e07e30..e5a1879 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -4,6 +4,8 @@
 import android.content.SharedPreferences
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener
 import androidx.annotation.VisibleForTesting
+import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
+import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
 import com.android.launcher3.allapps.WorkProfileManager
 import com.android.launcher3.model.DeviceGridState
 import com.android.launcher3.pm.InstallSessionHelper
@@ -20,11 +22,10 @@
 class LauncherPrefs(private val context: Context) {
 
     /** Wrapper around `getInner` for a `ContextualItem` */
-    fun <T : Any> get(item: ContextualItem<T>): T =
-        getInner(item, item.defaultValueFromContext(context))
+    fun <T> get(item: ContextualItem<T>): T = getInner(item, item.defaultValueFromContext(context))
 
     /** Wrapper around `getInner` for an `Item` */
-    fun <T : Any> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
+    fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
 
     /**
      * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
@@ -32,11 +33,11 @@
      * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
      */
     @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
-    private fun <T : Any> getInner(item: Item, default: T): T {
+    private fun <T> getInner(item: Item, default: T): T {
         val sp = context.getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE)
 
-        return when (default::class.java) {
-            String::class.java -> sp.getString(item.sharedPrefKey, default as String)
+        return when (item.type) {
+            String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
             Boolean::class.java,
             java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
             Int::class.java,
@@ -45,11 +46,10 @@
             java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
             Long::class.java,
             java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long)
-            Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as Set<String>)
+            Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
             else ->
                 throw IllegalArgumentException(
-                    "item type: ${default::class.java}" +
-                        " is not compatible with sharedPref methods"
+                    "item type: ${item.type}" + " is not compatible with sharedPref methods"
                 )
         }
             as T
@@ -224,39 +224,36 @@
             backedUpItem(RestoreDbTask.RESTORED_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE)
         @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
         @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
+        @JvmField val GRID_NAME = ConstantItem("idp_grid_name", true, null, String::class.java)
         @JvmField
         val ALLOW_ROTATION =
-            backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY) {
+            backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
                 RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
             }
 
         @VisibleForTesting
         @JvmStatic
         fun <T> backedUpItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
-            ConstantItem(sharedPrefKey, LauncherFiles.SHARED_PREFERENCES_KEY, defaultValue)
+            ConstantItem(sharedPrefKey, true, defaultValue)
 
         @JvmStatic
         fun <T> backedUpItem(
             sharedPrefKey: String,
+            type: Class<out T>,
             defaultValueFromContext: (c: Context) -> T
-        ): ContextualItem<T> =
-            ContextualItem(
-                sharedPrefKey,
-                LauncherFiles.SHARED_PREFERENCES_KEY,
-                defaultValueFromContext
-            )
+        ): ContextualItem<T> = ContextualItem(sharedPrefKey, true, defaultValueFromContext, type)
 
         @VisibleForTesting
         @JvmStatic
         fun <T> nonRestorableItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
-            ConstantItem(sharedPrefKey, LauncherFiles.DEVICE_PREFERENCES_KEY, defaultValue)
+            ConstantItem(sharedPrefKey, false, defaultValue)
 
         @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
         @JvmStatic
         fun getPrefs(context: Context): SharedPreferences {
             // Use application context for shared preferences, so we use single cached instance
             return context.applicationContext.getSharedPreferences(
-                LauncherFiles.SHARED_PREFERENCES_KEY,
+                SHARED_PREFERENCES_KEY,
                 Context.MODE_PRIVATE
             )
         }
@@ -266,7 +263,7 @@
         fun getDevicePrefs(context: Context): SharedPreferences {
             // Use application context for shared preferences, so we use a single cached instance
             return context.applicationContext.getSharedPreferences(
-                LauncherFiles.DEVICE_PREFERENCES_KEY,
+                DEVICE_PREFERENCES_KEY,
                 Context.MODE_PRIVATE
             )
         }
@@ -275,21 +272,27 @@
 
 abstract class Item {
     abstract val sharedPrefKey: String
-    abstract val sharedPrefFile: String
+    abstract val isBackedUp: Boolean
+    abstract val type: Class<*>
+    val sharedPrefFile: String
+        get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY
 
     fun <T> to(value: T): Pair<Item, T> = Pair(this, value)
 }
 
 data class ConstantItem<T>(
     override val sharedPrefKey: String,
-    override val sharedPrefFile: String,
-    val defaultValue: T
+    override val isBackedUp: Boolean,
+    val defaultValue: T,
+    // The default value can be null. If so, the type needs to be explicitly stated, or else NPE
+    override val type: Class<out T> = defaultValue!!::class.java
 ) : Item()
 
 data class ContextualItem<T>(
     override val sharedPrefKey: String,
-    override val sharedPrefFile: String,
-    private val defaultSupplier: (c: Context) -> T
+    override val isBackedUp: Boolean,
+    private val defaultSupplier: (c: Context) -> T,
+    override val type: Class<out T>
 ) : Item() {
     private var default: T? = null
 
diff --git a/src/com/android/launcher3/MultipageCellLayout.java b/src/com/android/launcher3/MultipageCellLayout.java
index a2c5255..0d59848 100644
--- a/src/com/android/launcher3/MultipageCellLayout.java
+++ b/src/com/android/launcher3/MultipageCellLayout.java
@@ -69,17 +69,14 @@
     @Override
     boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY, View dragView,
             int[] direction, boolean commit) {
+        // Add seam to x position
+        if (cellX > mCountX / 2) {
+            cellX++;
+        }
+        int finalCellX = cellX;
         return simulateSeam(
-                () -> super.createAreaForResize(cellX, cellY, spanX, spanY, dragView, direction,
-                        commit));
-    }
-
-    @Override
-    void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
-        simulateSeam(() -> {
-            super.regionToCenterPoint(cellX, cellY, spanX, spanY, result);
-            return 0;
-        });
+                () -> super.createAreaForResize(finalCellX, cellY, spanX, spanY, dragView,
+                        direction, commit));
     }
 
     @Override
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 0ec036c..cf5cf4e 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -65,98 +65,109 @@
      * Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"),
      * and set a default value for the flag. This will be the default value on Debug builds.
      */
+    // b/270390028
     public static final BooleanFlag ENABLE_INPUT_CONSUMER_REASON_LOGGING = getDebugFlag(
             "ENABLE_INPUT_CONSUMER_REASON_LOGGING",
             true,
             "Log the reason why an Input Consumer was selected for a gesture.");
 
+    // b/270389990
     public static final BooleanFlag ENABLE_GESTURE_ERROR_DETECTION = getDebugFlag(
             "ENABLE_GESTURE_ERROR_DETECTION",
             true,
             "Analyze gesture events and log detected errors");
 
     // When enabled the promise icon is visible in all apps while installation an app.
+    // b/270390012
     public static final BooleanFlag PROMISE_APPS_IN_ALL_APPS = getDebugFlag(
             "PROMISE_APPS_IN_ALL_APPS", false, "Add promise icon in all-apps");
 
+    // b/270390904
     public static final BooleanFlag KEYGUARD_ANIMATION = getDebugFlag(
             "KEYGUARD_ANIMATION", false, "Enable animation for keyguard going away on wallpaper");
 
+    // b/270390907
     public static final BooleanFlag ENABLE_DEVICE_SEARCH = new DeviceFlag(
             "ENABLE_DEVICE_SEARCH", true, "Allows on device search in all apps");
 
+    // b/270390286
     public static final BooleanFlag ENABLE_FLOATING_SEARCH_BAR =
             getDebugFlag("ENABLE_FLOATING_SEARCH_BAR", false,
                     "Keep All Apps search bar at the bottom (but above keyboard if open)");
 
+    // b/270390930
     public static final BooleanFlag ENABLE_HIDE_HEADER = new DeviceFlag("ENABLE_HIDE_HEADER",
             true, "Hide header on keyboard before typing in all apps");
 
+    // b/270390779
     public static final BooleanFlag ENABLE_EXPANDING_PAUSE_WORK_BUTTON = new DeviceFlag(
             "ENABLE_EXPANDING_PAUSE_WORK_BUTTON", false,
             "Expand and collapse pause work button while scrolling");
 
+    // b/270390950
     public static final BooleanFlag ENABLE_RECENT_BLOCK = getDebugFlag("ENABLE_RECENT_BLOCK",
             false, "Show recently tapped search target block in zero state");
 
+    // b/270391455
     public static final BooleanFlag COLLECT_SEARCH_HISTORY = new DeviceFlag(
             "COLLECT_SEARCH_HISTORY", false, "Allow launcher to collect search history for log");
 
+    // b/270390937
     public static final BooleanFlag ENABLE_TWOLINE_ALLAPPS = getDebugFlag(
             "ENABLE_TWOLINE_ALLAPPS", false, "Enables two line label inside all apps.");
 
+    // b/270391397
     public static final BooleanFlag ENABLE_DEVICE_SEARCH_PERFORMANCE_LOGGING = new DeviceFlag(
             "ENABLE_DEVICE_SEARCH_PERFORMANCE_LOGGING", false,
             "Allows on device search in all apps logging");
 
+    // b/270391693
     public static final BooleanFlag IME_STICKY_SNACKBAR_EDU = getDebugFlag(
             "IME_STICKY_SNACKBAR_EDU", true, "Show sticky IME edu in AllApps");
 
+    // b/270391653
     public static final BooleanFlag ENABLE_PEOPLE_TILE_PREVIEW = getDebugFlag(
             "ENABLE_PEOPLE_TILE_PREVIEW", false,
             "Experimental: Shows conversation shortcuts on home screen as search results");
 
+    // b/270391638
     public static final BooleanFlag FOLDER_NAME_MAJORITY_RANKING = getDebugFlag(
             "FOLDER_NAME_MAJORITY_RANKING", true,
             "Suggests folder names based on majority based ranking.");
 
+    // b/270391706
     public static final BooleanFlag INJECT_FALLBACK_APP_CORPUS_RESULTS = new DeviceFlag(
             "INJECT_FALLBACK_APP_CORPUS_RESULTS", false, "Inject "
             + "fallback app corpus result when AiAi fails to return it.");
 
+    // b/270391641
     public static final BooleanFlag ASSISTANT_GIVES_LAUNCHER_FOCUS = getDebugFlag(
             "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
             "Allow Launcher to handle nav bar gestures while Assistant is running over it");
 
+    // b/270392203
     public static final BooleanFlag ENABLE_BULK_WORKSPACE_ICON_LOADING = getDebugFlag(
             "ENABLE_BULK_WORKSPACE_ICON_LOADING",
             true,
             "Enable loading workspace icons in bulk.");
 
+    // b/270392465
     public static final BooleanFlag ENABLE_BULK_ALL_APPS_ICON_LOADING = getDebugFlag(
             "ENABLE_BULK_ALL_APPS_ICON_LOADING",
             true,
             "Enable loading all apps 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");
-
-    public static final BooleanFlag ENABLE_WIDGETS_PICKER_AIAI_SEARCH = new DeviceFlag(
-            "ENABLE_WIDGETS_PICKER_AIAI_SEARCH", true, "Enable AiAi search in the widgets picker");
-
-    public static final BooleanFlag ENABLE_OVERVIEW_SHARING_TO_PEOPLE = getDebugFlag(
-            "ENABLE_OVERVIEW_SHARING_TO_PEOPLE", true,
-            "Show indicators for content on Overview to share with top people. ");
-
+    // b/270392706
     public static final BooleanFlag ENABLE_DATABASE_RESTORE = getDebugFlag(
             "ENABLE_DATABASE_RESTORE", false,
             "Enable database restore when new restore session is created");
 
+    // b/270391664
     public static final BooleanFlag ENABLE_SMARTSPACE_DISMISS = getDebugFlag(
             "ENABLE_SMARTSPACE_DISMISS", true,
             "Adds a menu option to dismiss the current Enhanced Smartspace card.");
 
+    // b/270392629
     public static final BooleanFlag ENABLE_OVERLAY_CONNECTION_OPTIM = getDebugFlag(
             "ENABLE_OVERLAY_CONNECTION_OPTIM",
             false,
@@ -165,251 +176,296 @@
     /**
      * Enables region sampling for text color: Needs system health assessment before turning on
      */
+    // b/270391669
     public static final BooleanFlag ENABLE_REGION_SAMPLING = getDebugFlag(
             "ENABLE_REGION_SAMPLING", false,
             "Enable region sampling to determine color of text on screen.");
 
+    // b/270393096
     public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS =
             getDebugFlag(
                     "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
                     "Always use hardware optimization for folder animations.");
 
+    // b/270392980
     public static final BooleanFlag SEPARATE_RECENTS_ACTIVITY = getDebugFlag(
             "SEPARATE_RECENTS_ACTIVITY", false,
             "Uses a separate recents activity instead of using the integrated recents+Launcher UI");
 
+    // b/270392984
     public static final BooleanFlag ENABLE_MINIMAL_DEVICE = getDebugFlag(
             "ENABLE_MINIMAL_DEVICE", false,
             "Allow user to toggle minimal device mode in launcher.");
 
-    // TODO: b/172467144 Remove ENABLE_LAUNCHER_ACTIVITY_THEME_CROSSFADE feature flag.
-    public static final BooleanFlag ENABLE_LAUNCHER_ACTIVITY_THEME_CROSSFADE = new DeviceFlag(
-            "ENABLE_LAUNCHER_ACTIVITY_THEME_CROSSFADE", false, "Enables a "
-            + "crossfade animation when the system these changes.");
-
-    // TODO: b/174174514 Remove ENABLE_APP_PREDICTIONS_WHILE_VISIBLE feature flag.
-    public static final BooleanFlag ENABLE_APP_PREDICTIONS_WHILE_VISIBLE = new DeviceFlag(
-            "ENABLE_APP_PREDICTIONS_WHILE_VISIBLE", true, "Allows app "
-            + "predictions to be updated while they are visible to the user.");
-
+    // b/270392477
     public static final BooleanFlag ENABLE_TASKBAR_POPUP_MENU = getDebugFlag(
             "ENABLE_TASKBAR_POPUP_MENU", true, "Enables long pressing taskbar icons to show the"
                     + " popup menu.");
 
+    // b/270392643
     public static final BooleanFlag ENABLE_TWO_PANEL_HOME = getDebugFlag(
             "ENABLE_TWO_PANEL_HOME", true,
             "Uses two panel on home screen. Only applicable on large screen devices.");
 
+    // b/270393276
     public static final BooleanFlag ENABLE_SCRIM_FOR_APP_LAUNCH = getDebugFlag(
             "ENABLE_SCRIM_FOR_APP_LAUNCH", false,
             "Enables scrim during app launch animation.");
 
+    // b/270393258
     public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = new DeviceFlag(
             "ENABLE_ENFORCED_ROUNDED_CORNERS", true, "Enforce rounded corners on all App Widgets");
 
+    // b/270393108
     public static final BooleanFlag NOTIFY_CRASHES = getDebugFlag("NOTIFY_CRASHES", false,
             "Sends a notification whenever launcher encounters an uncaught exception.");
 
+    // b/270393604
     public static final BooleanFlag ENABLE_WALLPAPER_SCRIM = getDebugFlag(
             "ENABLE_WALLPAPER_SCRIM", false,
             "Enables scrim over wallpaper for text protection.");
 
+    // b/270393268
     public static final BooleanFlag WIDGETS_IN_LAUNCHER_PREVIEW = getDebugFlag(
             "WIDGETS_IN_LAUNCHER_PREVIEW", true,
             "Enables widgets in Launcher preview for the Wallpaper app.");
 
+    // b/270393112
     public static final BooleanFlag QUICK_WALLPAPER_PICKER = getDebugFlag(
             "QUICK_WALLPAPER_PICKER", true,
             "Shows quick wallpaper picker in long-press menu");
 
+    // b/270393426
     public static final BooleanFlag ENABLE_BACK_SWIPE_HOME_ANIMATION = getDebugFlag(
             "ENABLE_BACK_SWIPE_HOME_ANIMATION", true,
             "Enables home animation to icon when user swipes back.");
 
+    // b/270393294
     public static final BooleanFlag ENABLE_ICON_LABEL_AUTO_SCALING = getDebugFlag(
             "ENABLE_ICON_LABEL_AUTO_SCALING", true,
             "Enables scaling/spacing for icon labels to make more characters visible");
 
+    // b/270393897
     public static final BooleanFlag ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT = getDebugFlag(
             "ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT", false,
             "Enables displaying the all apps button in the hotseat.");
 
+    // b/270393900
     public static final BooleanFlag ENABLE_ALL_APPS_ONE_SEARCH_IN_TASKBAR = getDebugFlag(
             "ENABLE_ALL_APPS_ONE_SEARCH_IN_TASKBAR", false,
             "Enables One Search box in Taskbar All Apps.");
 
+    // b/270393449
     public static final BooleanFlag ENABLE_TASKBAR_IN_OVERVIEW = getDebugFlag(
             "ENABLE_TASKBAR_IN_OVERVIEW", true,
             "Enables accessing the system Taskbar in overview.");
 
+    // b/270393906
     public static final BooleanFlag ENABLE_SPLIT_FROM_WORKSPACE = getDebugFlag(
             "ENABLE_SPLIT_FROM_WORKSPACE", true,
             "Enable initiating split screen from workspace.");
 
+    // b/270394122
     public static final BooleanFlag ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS =
             getDebugFlag("ENABLE_SPLIT_FROM_FULLSCREEN_SHORTCUT", false,
                     "Enable splitting from fullscreen app with keyboard shortcuts");
 
+    // b/270393453
     public static final BooleanFlag ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE = getDebugFlag(
             "ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE", false,
             "Enable initiating split screen from workspace to workspace.");
 
+    // b/270393455
     public static final BooleanFlag ENABLE_NEW_MIGRATION_LOGIC = getDebugFlag(
             "ENABLE_NEW_MIGRATION_LOGIC", true,
             "Enable the new grid migration logic, keeping pages when src < dest");
 
+    // b/270394384
     public static final BooleanFlag ENABLE_WIDGET_HOST_IN_BACKGROUND = getDebugFlag(
             "ENABLE_WIDGET_HOST_IN_BACKGROUND", false,
             "Enable background widget updates listening for widget holder");
 
+    // b/270394223
     public static final BooleanFlag ENABLE_ONE_SEARCH_MOTION = new DeviceFlag(
             "ENABLE_ONE_SEARCH_MOTION", true, "Enables animations in OneSearch.");
 
+    // b/270394041
     public static final BooleanFlag ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES = new DeviceFlag(
             "ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", false,
             "Enable option to replace decorator-based search result backgrounds with drawables");
 
+    // b/270394392
     public static final BooleanFlag ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION = new DeviceFlag(
             "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", false,
             "Enable option to launch search results using the new view container transitions");
 
+    // b/270394225
     public static final BooleanFlag TWO_PREDICTED_ROWS_ALL_APPS_SEARCH = new DeviceFlag(
             "TWO_PREDICTED_ROWS_ALL_APPS_SEARCH", false,
             "Use 2 rows of app predictions in All Apps search zero-state");
 
+    // b/270394468
     public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = new DeviceFlag(
             "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", true,
             "Enable option to show keyboard when going to all-apps");
 
+    // b/270394973
     public static final BooleanFlag USE_LOCAL_ICON_OVERRIDES = getDebugFlag(
             "USE_LOCAL_ICON_OVERRIDES", true,
             "Use inbuilt monochrome icons if app doesn't provide one");
 
+    // b/270394476
     public static final BooleanFlag ENABLE_DISMISS_PREDICTION_UNDO = getDebugFlag(
             "ENABLE_DISMISS_PREDICTION_UNDO", false,
             "Show an 'Undo' snackbar when users dismiss a predicted hotseat item");
 
+    // b/270395008
     public static final BooleanFlag ENABLE_CACHED_WIDGET = getDebugFlag(
             "ENABLE_CACHED_WIDGET", true,
             "Show previously cached widgets as opposed to deferred widget where available");
 
+    // b/270395010
     public static final BooleanFlag USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES = getDebugFlag(
             "USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES", false,
             "Use local overrides for search request timeout");
 
+    // b/270395171
     public static final BooleanFlag CONTINUOUS_VIEW_TREE_CAPTURE = getDebugFlag(
             "CONTINUOUS_VIEW_TREE_CAPTURE", false, "Capture View tree every frame");
 
+    // b/270395070
     public static final BooleanFlag FOLDABLE_WORKSPACE_REORDER = getDebugFlag(
             "FOLDABLE_WORKSPACE_REORDER", true,
             "In foldables, when reordering the icons and widgets, is now going to use both sides");
 
-    public static final BooleanFlag ENABLE_WIDGET_PICKER_DEPTH = new DeviceFlag(
-            "ENABLE_WIDGET_PICKER_DEPTH", true, "Enable changing depth in widget picker.");
-
+    // b/270395073
     public static final BooleanFlag ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH = getDebugFlag(
             "ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH", false,
             "Allow bottom sheet depth to be smaller than 1 for multi-display devices.");
 
+    // b/270395177
     public static final BooleanFlag SCROLL_TOP_TO_RESET = new DeviceFlag(
             "SCROLL_TOP_TO_RESET", true, "Bring up IME and focus on "
             + "input when scroll to top if 'Always show keyboard' is enabled or in prefix state");
 
+    // b/270395516
     public static final BooleanFlag ENABLE_MATERIAL_U_POPUP = getDebugFlag(
             "ENABLE_MATERIAL_U_POPUP", false, "Switch popup UX to use material U");
 
+    // b/270395269
     public static final BooleanFlag ENABLE_SEARCH_UNINSTALLED_APPS = new DeviceFlag(
             "ENABLE_SEARCH_UNINSTALLED_APPS", false, "Search uninstalled app results.");
 
+    // b/270395183
     public static final BooleanFlag SHOW_HOME_GARDENING = getDebugFlag(
             "SHOW_HOME_GARDENING", false,
             "Show the new home gardening mode");
 
+    // b/270395133
     public static final BooleanFlag HOME_GARDENING_WORKSPACE_BUTTONS = getDebugFlag(
             "HOME_GARDENING_WORKSPACE_BUTTONS", false,
             "Change workspace edit buttons to reflect home gardening");
 
+    // b/270395134
     public static final BooleanFlag ENABLE_DOWNLOAD_APP_UX_V2 = new DeviceFlag(
             "ENABLE_DOWNLOAD_APP_UX_V2", true, "Updates the download app UX"
                     + " to have better visuals");
 
+    // b/270395186
     public static final BooleanFlag ENABLE_DOWNLOAD_APP_UX_V3 = getDebugFlag(
             "ENABLE_DOWNLOAD_APP_UX_V3", false, "Updates the download app UX"
             + " to have better visuals, improve contrast, and color");
 
+    // b/270395077
     public static final BooleanFlag FORCE_PERSISTENT_TASKBAR = getDebugFlag(
             "FORCE_PERSISTENT_TASKBAR", false, "Forces taskbar to be persistent, even in gesture"
                     + " nav mode and when transient taskbar is enabled.");
 
+    // b/270395274
     public static final BooleanFlag FOLDABLE_SINGLE_PAGE = getDebugFlag(
             "FOLDABLE_SINGLE_PAGE", false,
             "Use a single page for the workspace");
 
+    // b/270395798
     public static final BooleanFlag ENABLE_TRANSIENT_TASKBAR = getDebugFlag(
             "ENABLE_TRANSIENT_TASKBAR", true, "Enables transient taskbar.");
 
+    // b/270395140
     public static final BooleanFlag SECONDARY_DRAG_N_DROP_TO_PIN = getDebugFlag(
             "SECONDARY_DRAG_N_DROP_TO_PIN", false,
             "Enable dragging and dropping to pin apps within secondary display");
 
+    // b/270395143
     public static final BooleanFlag ENABLE_ICON_IN_TEXT_HEADER = getDebugFlag(
             "ENABLE_ICON_IN_TEXT_HEADER", false, "Show icon in textheader");
 
+    // b/270395087
     public static final BooleanFlag ENABLE_APP_ICON_FOR_INLINE_SHORTCUTS = getDebugFlag(
             "ENABLE_APP_ICON_IN_INLINE_SHORTCUTS", false, "Show app icon for inline shortcut");
 
+    // b/270395278
     public static final BooleanFlag SHOW_DOT_PAGINATION = getDebugFlag(
             "SHOW_DOT_PAGINATION", false, "Enable showing dot pagination in workspace");
 
+    // b/270395809
     public static final BooleanFlag LARGE_SCREEN_WIDGET_PICKER = getDebugFlag(
             "LARGE_SCREEN_WIDGET_PICKER", false, "Enable new widget picker that takes "
                     + "advantage of large screen format");
 
+    // b/270396257
     public static final BooleanFlag ENABLE_NEW_GESTURE_NAV_TUTORIAL = getDebugFlag(
             "ENABLE_NEW_GESTURE_NAV_TUTORIAL", false,
             "Enable the redesigned gesture navigation tutorial");
 
-    public static final BooleanFlag ENABLE_DEVICE_PROFILE_LOGGING = new DeviceFlag(
-            "ENABLE_DEVICE_PROFILE_LOGGING", false, "Allows DeviceProfile logging");
-
+    // b/270395567
     public static final BooleanFlag ENABLE_LAUNCH_FROM_STAGED_APP = getDebugFlag(
             "ENABLE_LAUNCH_FROM_STAGED_APP", true,
             "Enable the ability to tap a staged app during split select to launch it in full screen"
     );
+
+    // b/270396358
     public static final BooleanFlag ENABLE_HAPTICS_ALL_APPS = getDebugFlag(
             "ENABLE_HAPTICS_ALL_APPS", false, "Enables haptics opening/closing All apps");
 
+    // b/270396209
     public static final BooleanFlag ENABLE_FORCED_MONO_ICON = getDebugFlag(
             "ENABLE_FORCED_MONO_ICON", false,
             "Enable the ability to generate monochromatic icons, if it is not provided by the app"
     );
 
+    // b/270396364
     public static final BooleanFlag ENABLE_DREAM_TRANSITION = getDebugFlag(
             "ENABLE_DREAM_TRANSITION", true,
             "Enable the launcher transition when the device enters a dream");
 
+    // b/270396268
     public static final BooleanFlag ENABLE_TASKBAR_EDU_TOOLTIP = getDebugFlag(
             "ENABLE_TASKBAR_EDU_TOOLTIP", true,
             "Enable the tooltip version of the Taskbar education flow.");
 
+    // b/270396680
     public static final BooleanFlag ENABLE_MULTI_INSTANCE = getDebugFlag(
             "ENABLE_MULTI_INSTANCE", false,
             "Enables creation and filtering of multiple task instances in overview");
 
+    // b/270396583
     public static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(
             "ENABLE_TASKBAR_PINNING", false,
             "Enables taskbar pinning to allow user to switch between transient and persistent "
                     + "taskbar flavors");
 
+    // b/270397206
     public static final BooleanFlag ENABLE_GRID_ONLY_OVERVIEW = getDebugFlag(
             "ENABLE_GRID_ONLY_OVERVIEW", false,
             "Enable a grid-only overview without a focused task.");
 
+    // b/270397209
     public static final BooleanFlag RECEIVE_UNFOLD_EVENTS_FROM_SYSUI = getDebugFlag(
             "RECEIVE_UNFOLD_EVENTS_FROM_SYSUI", true,
             "Enables receiving unfold animation events from sysui instead of calculating "
                     + "them in launcher process using hinge sensor values.");
 
+    // b/270396844
     public static final BooleanFlag ENABLE_KEYBOARD_QUICK_SWITCH = getDebugFlag(
             "ENABLE_KEYBOARD_QUICK_SWITCH", false,
             "Enables keyboard quick switching");
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index c23ea8a..a0f21dc 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -20,6 +20,8 @@
 
 import static com.android.launcher3.anim.Interpolators.ACCELERATED_EASE;
 import static com.android.launcher3.anim.Interpolators.DECELERATED_EASE;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_MATERIAL_U_POPUP;
 
@@ -37,11 +39,13 @@
 import android.graphics.drawable.GradientDrawable;
 import android.util.AttributeSet;
 import android.util.Pair;
+import android.util.Property;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
 import android.widget.FrameLayout;
 
 import androidx.annotation.Nullable;
@@ -81,6 +85,19 @@
     protected int CLOSE_CHILD_FADE_START_DELAY = 0;
     protected int CLOSE_CHILD_FADE_DURATION = 140;
 
+    private static final int OPEN_DURATION_U = 200;
+    private static final int OPEN_FADE_START_DELAY_U = 0;
+    private static final int OPEN_FADE_DURATION_U = 83;
+    private static final int OPEN_CHILD_FADE_START_DELAY_U = 0;
+    private static final int OPEN_CHILD_FADE_DURATION_U = 83;
+    private static final int OPEN_OVERSHOOT_DURATION_U = 200;
+
+    private static final int CLOSE_DURATION_U  = 233;
+    private static final int CLOSE_FADE_START_DELAY_U = 150;
+    private static final int CLOSE_FADE_DURATION_U = 83;
+    private static final int CLOSE_CHILD_FADE_START_DELAY_U = 150;
+    private static final int CLOSE_CHILD_FADE_DURATION_U = 83;
+
     protected final Rect mTempRect = new Rect();
 
     protected final LayoutInflater mInflater;
@@ -104,6 +121,7 @@
     protected AnimatorSet mOpenCloseAnimator;
     protected boolean mDeferContainerRemoval;
     protected boolean shouldScaleArrow = false;
+    protected boolean mIsArrowRotated = false;
 
     private final GradientDrawable mRoundedTop;
     private final GradientDrawable mRoundedBottom;
@@ -591,10 +609,24 @@
 
     protected void animateOpen() {
         setVisibility(View.VISIBLE);
+        mOpenCloseAnimator = ENABLE_MATERIAL_U_POPUP.get()
+                ? getMaterialUOpenCloseAnimator(
+                        true,
+                        OPEN_DURATION_U,
+                        OPEN_FADE_START_DELAY_U,
+                        OPEN_FADE_DURATION_U,
+                        OPEN_CHILD_FADE_START_DELAY_U,
+                        OPEN_CHILD_FADE_DURATION_U,
+                        EMPHASIZED_DECELERATE)
+                : getOpenCloseAnimator(
+                        true,
+                        OPEN_DURATION,
+                        OPEN_FADE_START_DELAY,
+                        OPEN_FADE_DURATION,
+                        OPEN_CHILD_FADE_START_DELAY,
+                        OPEN_CHILD_FADE_DURATION,
+                        DECELERATED_EASE);
 
-        mOpenCloseAnimator = getOpenCloseAnimator(true, OPEN_DURATION, OPEN_FADE_START_DELAY,
-                OPEN_FADE_DURATION, OPEN_CHILD_FADE_START_DELAY, OPEN_CHILD_FADE_DURATION,
-                DECELERATED_EASE);
         onCreateOpenAnimation(mOpenCloseAnimator);
         mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -680,6 +712,24 @@
         mOpenCloseAnimator = getOpenCloseAnimator(false, CLOSE_DURATION, CLOSE_FADE_START_DELAY,
                 CLOSE_FADE_DURATION, CLOSE_CHILD_FADE_START_DELAY, CLOSE_CHILD_FADE_DURATION,
                 ACCELERATED_EASE);
+
+        mOpenCloseAnimator = ENABLE_MATERIAL_U_POPUP.get()
+                ? getMaterialUOpenCloseAnimator(
+                        false,
+                        CLOSE_DURATION_U,
+                        CLOSE_FADE_START_DELAY_U,
+                        CLOSE_FADE_DURATION_U,
+                        CLOSE_CHILD_FADE_START_DELAY_U,
+                        CLOSE_CHILD_FADE_DURATION_U,
+                        EMPHASIZED_ACCELERATE)
+                : getOpenCloseAnimator(false,
+                        CLOSE_DURATION,
+                        CLOSE_FADE_START_DELAY,
+                        CLOSE_FADE_DURATION,
+                        CLOSE_CHILD_FADE_START_DELAY,
+                        CLOSE_CHILD_FADE_DURATION,
+                        ACCELERATED_EASE);
+
         onCreateCloseAnimation(mOpenCloseAnimator);
         mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -695,6 +745,59 @@
         mOpenCloseAnimator.start();
     }
 
+    protected AnimatorSet getMaterialUOpenCloseAnimator(boolean isOpening, int scaleDuration,
+            int fadeStartDelay, int fadeDuration, int childFadeStartDelay, int childFadeDuration,
+            Interpolator interpolator) {
+
+        int arrowCenter = mArrowOffsetHorizontal + mArrowWidth / 2;
+        if (mIsArrowRotated) {
+            setPivotX(mIsLeftAligned ? 0f : getMeasuredWidth());
+            setPivotY(arrowCenter);
+        } else {
+            setPivotX(mIsLeftAligned ? arrowCenter : getMeasuredWidth() - arrowCenter);
+            setPivotY(mIsAboveIcon ? getMeasuredHeight() : 0f);
+        }
+
+        float[] alphaValues = isOpening ? new float[] {0, 1} : new float[] {1, 0};
+        float[] scaleValues = isOpening ? new float[] {0.5f, 1.02f} : new float[] {1f, 0.5f};
+        Animator alpha = getAnimatorOfFloat(this, View.ALPHA, fadeDuration, fadeStartDelay,
+                LINEAR, alphaValues);
+        Animator arrowAlpha = getAnimatorOfFloat(mArrow, View.ALPHA, fadeDuration, fadeStartDelay,
+                LINEAR, alphaValues);
+        Animator scaleY = getAnimatorOfFloat(this, View.SCALE_Y, scaleDuration, 0, interpolator,
+                scaleValues);
+        Animator scaleX = getAnimatorOfFloat(this, View.SCALE_X, scaleDuration, 0, interpolator,
+                scaleValues);
+
+        final AnimatorSet animatorSet = new AnimatorSet();
+        if (isOpening) {
+            float[] scaleValuesOvershoot = new float[] {1.02f, 1f};
+            PathInterpolator overshootInterpolator = new PathInterpolator(0.3f, 0, 0.33f, 1f);
+            Animator overshootY = getAnimatorOfFloat(this, View.SCALE_Y,
+                    OPEN_OVERSHOOT_DURATION_U, scaleDuration, overshootInterpolator,
+                    scaleValuesOvershoot);
+            Animator overshootX = getAnimatorOfFloat(this, View.SCALE_X,
+                    OPEN_OVERSHOOT_DURATION_U, scaleDuration, overshootInterpolator,
+                    scaleValuesOvershoot);
+
+            animatorSet.playTogether(alpha, arrowAlpha, scaleY, scaleX, overshootX, overshootY);
+        } else {
+            animatorSet.playTogether(alpha, arrowAlpha, scaleY, scaleX);
+        }
+
+        fadeInChildViews(this, alphaValues, childFadeStartDelay, childFadeDuration, animatorSet);
+        return animatorSet;
+    }
+
+    private Animator getAnimatorOfFloat(View view, Property<View, Float> property,
+            int duration, int startDelay, Interpolator interpolator,  float... values) {
+        Animator animator = ObjectAnimator.ofFloat(view, property, values);
+        animator.setDuration(duration);
+        animator.setInterpolator(interpolator);
+        animator.setStartDelay(startDelay);
+        return animator;
+    }
+
     /**
      * Called when creating the open transition allowing subclass can add additional animations.
      */
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
new file mode 100644
index 0000000..7b49583
--- /dev/null
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -0,0 +1,57 @@
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.Intent
+import android.os.Process
+import android.os.UserManager
+import androidx.annotation.VisibleForTesting
+
+class LockedUserState(private val mContext: Context) : SafeCloseable {
+    var isUserUnlocked: Boolean
+        private set
+    private val mUserUnlockedActions: RunnableList = RunnableList()
+
+    @VisibleForTesting
+    val mUserUnlockedReceiver = SimpleBroadcastReceiver {
+        if (Intent.ACTION_USER_UNLOCKED == it.action) {
+            isUserUnlocked = true
+            notifyUserUnlocked()
+        }
+    }
+
+    init {
+        isUserUnlocked =
+            mContext
+                .getSystemService(UserManager::class.java)!!
+                .isUserUnlocked(Process.myUserHandle())
+        if (isUserUnlocked) {
+            notifyUserUnlocked()
+        } else {
+            mUserUnlockedReceiver.register(mContext, Intent.ACTION_USER_UNLOCKED)
+        }
+    }
+
+    private fun notifyUserUnlocked() {
+        mUserUnlockedActions.executeAllAndDestroy()
+        mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
+    }
+
+    /** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */
+    override fun close() {
+        mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
+    }
+
+    /**
+     * Adds a `Runnable` to be executed when a user is unlocked. If the user is already unlocked,
+     * this runnable will run immediately because RunnableList will already have been destroyed.
+     */
+    fun runOnUserUnlocked(action: Runnable) {
+        mUserUnlockedActions.add(action)
+    }
+
+    companion object {
+        @VisibleForTesting val INSTANCE = MainThreadInitializedObject { LockedUserState(it) }
+
+        @JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context)
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index d72914b..f8068aa 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -46,8 +46,8 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.Button;
+import android.widget.FrameLayout;
 import android.widget.LinearLayout;
-import android.widget.ScrollView;
 import android.widget.TextView;
 
 import androidx.annotation.FloatRange;
@@ -67,6 +67,7 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.recyclerview.ViewHolderBinder;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.views.ArrowTipView;
 import com.android.launcher3.views.RecyclerViewFastScroller;
@@ -198,7 +199,7 @@
     private View mSearchBarContainer;
     private WidgetsSearchBar mSearchBar;
     private TextView mHeaderTitle;
-    private ScrollView mRightPane;
+    private FrameLayout mRightPane;
     private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
     private final boolean mIsTwoPane;
 
@@ -293,7 +294,9 @@
 
             // Inflate the suggestions header.
             mSuggestedWidgetsHeader = (WidgetsListHeader) layoutInflater.inflate(
-                    R.layout.widgets_list_row_header, mSuggestedWidgetsContainer, false);
+                    R.layout.widgets_list_row_header_two_pane,
+                    mSuggestedWidgetsContainer,
+                    false);
             mSuggestedWidgetsHeader.setExpanded(true);
 
             PackageItemInfo packageItemInfo =  new PackageItemInfo(
@@ -314,8 +317,8 @@
             mSuggestedWidgetsHeader.applyFromItemInfoWithIcon(widgetsListHeaderEntry);
             mSuggestedWidgetsHeader.setIcon(
                     getContext().getDrawable(R.drawable.widget_suggestions_icon));
-            mSuggestedWidgetsHeader.setOnExpandChangeListener(isExpanded -> {
-                mSuggestedWidgetsHeader.setExpanded(isExpanded);
+            mSuggestedWidgetsHeader.setOnClickListener(view -> {
+                mSuggestedWidgetsHeader.setExpanded(true);
                 resetExpandedHeaders();
                 mRightPane.removeAllViews();
                 mRightPane.addView(mRecommendedWidgetsTable);
@@ -389,6 +392,11 @@
     private void updateRecyclerViewVisibility(AdapterHolder adapterHolder) {
         // The first item is always an empty space entry. Look for any more items.
         boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries();
+
+        if (mIsTwoPane) {
+            mRightPane.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
+        }
+
         adapterHolder.mWidgetsRecyclerView.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
 
         if (adapterHolder.mAdapterType == AdapterHolder.SEARCH) {
@@ -979,11 +987,13 @@
                             mWidgetsListTableViewHolderBinder.newViewHolder(mRightPane);
                     mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
                             contentEntry,
-                            0, Collections.EMPTY_LIST);
+                            ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
+                            Collections.EMPTY_LIST);
                     widgetsRowViewHolder.mDataCallback = data -> {
                         mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
                                 contentEntry,
-                                0, Collections.singletonList(data));
+                                ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
+                                Collections.singletonList(data));
                     };
                     mRightPane.removeAllViews();
                     mRightPane.addView(widgetsRowViewHolder.itemView);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 20b1d9b..c28402e 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -115,7 +115,8 @@
         mViewHolderBinders.put(
                 VIEW_TYPE_WIDGETS_HEADER,
                 new WidgetsListHeaderViewHolderBinder(
-                        layoutInflater, /* onHeaderClickListener= */ this));
+                        layoutInflater, /* onHeaderClickListener= */ this,
+                        headerChangeListener != null));
         mViewHolderBinders.put(
                 VIEW_TYPE_WIDGETS_SPACE,
                 new WidgetsSpaceViewHolderBinder(emptySpaceHeightProvider));
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListDrawableState.java b/src/com/android/launcher3/widget/picker/WidgetsListDrawableState.java
index 94f292b..ca69ffe 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListDrawableState.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListDrawableState.java
@@ -21,9 +21,7 @@
  */
 enum WidgetsListDrawableState {
     FIRST(new int[]{android.R.attr.state_first}),
-    FIRST_EXPANDED(new int[]{android.R.attr.state_first, android.R.attr.state_expanded}),
     MIDDLE(new int[]{android.R.attr.state_middle}),
-    MIDDLE_EXPANDED(new int[]{android.R.attr.state_middle, android.R.attr.state_expanded}),
     LAST(new int[]{android.R.attr.state_last}),
     SINGLE(new int[]{android.R.attr.state_single});
 
@@ -33,12 +31,10 @@
         mStateSet = stateSet;
     }
 
-    static WidgetsListDrawableState obtain(boolean isFirst, boolean isLast, boolean isExpanded) {
+    static WidgetsListDrawableState obtain(boolean isFirst, boolean isLast) {
         if (isFirst && isLast) return SINGLE;
-        if (isFirst && isExpanded) return FIRST_EXPANDED;
         if (isFirst) return FIRST;
         if (isLast) return LAST;
-        if (isExpanded) return MIDDLE_EXPANDED;
         return MIDDLE;
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index 547f39e..b5e7401 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -15,17 +15,12 @@
  */
 package com.android.launcher3.widget.picker;
 
-import static com.android.launcher3.config.FeatureFlags.LARGE_SCREEN_WIDGET_PICKER;
-
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.graphics.Color;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.util.TypedValue;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ImageView;
@@ -38,13 +33,11 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.icons.PlaceHolderIconDrawable;
 import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
 
@@ -56,8 +49,9 @@
  */
 public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpdateReceiver {
 
+    private static final int[] EXPANDED_DRAWABLE_STATE = new int[] {android.R.attr.state_expanded};
+
     private final int mIconSize;
-    private final boolean mIsTwoPane;
 
     @Nullable private HandlerRunnable mIconLoadRequest;
     @Nullable private Drawable mIconDrawable;
@@ -65,7 +59,6 @@
     private ImageView mAppIcon;
     private TextView mTitle;
     private TextView mSubtitle;
-    private GradientDrawable mBackground;
     private boolean mEnableIconUpdateAnimation = false;
     private boolean mIsExpanded = false;
 
@@ -86,11 +79,6 @@
                 R.styleable.WidgetsListRowHeader, defStyleAttr, /* defStyleRes= */ 0);
         mIconSize = a.getDimensionPixelSize(R.styleable.WidgetsListRowHeader_appIconSize,
                 grid.iconSizePx);
-
-        mIsTwoPane = grid.isLandscape && grid.isTablet && LARGE_SCREEN_WIDGET_PICKER.get();
-        if (mIsTwoPane) {
-            setLargeScreenTheme();
-        }
     }
 
     @Override
@@ -99,9 +87,6 @@
         mAppIcon = findViewById(R.id.app_icon);
         mTitle = findViewById(R.id.app_title);
         mSubtitle = findViewById(R.id.app_subtitle);
-        if (mIsTwoPane) {
-            findViewById(R.id.toggle).setVisibility(GONE);
-        }
         setAccessibilityDelegate(new AccessibilityDelegate() {
 
             @Override
@@ -130,58 +115,18 @@
         });
     }
 
-    /**
-     * Sets a {@link OnExpansionChangeListener} to get a callback when this app widgets section
-     * expands / collapses.
-     */
-    @UiThread
-    public void setOnExpandChangeListener(
-            @Nullable OnExpansionChangeListener onExpandChangeListener) {
-        // Use the entire touch area of this view to expand / collapse an app widgets section.
-        setOnClickListener(view -> {
-            setExpanded(mIsTwoPane || !mIsExpanded);
-            if (onExpandChangeListener != null) {
-                onExpandChangeListener.onExpansionChange(mIsExpanded);
-            }
-        });
-    }
-
     /** Sets the expand toggle to expand / collapse. */
     @UiThread
     public void setExpanded(boolean isExpanded) {
         this.mIsExpanded = isExpanded;
-        if (mIsTwoPane) {
-            if (Utilities.isDarkTheme(getContext())) {
-                if (mIsExpanded) {
-                    mTitle.setTextColor(Color.BLACK);
-                    mSubtitle.setTextColor(Color.BLACK);
-                } else {
-                    mTitle.setTextColor(Color.WHITE);
-                    mSubtitle.setTextColor(Themes.getAttrColor(getContext(),
-                            android.R.attr.textColorSecondary));
-                }
-            }
-            setLargeScreenTheme();
-        }
+        refreshDrawableState();
     }
 
-    /**
-     * Sets the style for the header when we are using large screens in landscape.
-     */
-    private void setLargeScreenTheme() {
-        if (mBackground == null) {
-            mBackground = new GradientDrawable();
-            mBackground.setCornerRadius((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                    28,
-                    getContext().getResources().getDisplayMetrics()));
-        }
-        mBackground.setColor(mIsExpanded
-                ? getResources().getColor(R.color.widget_picker_background_selected)
-                : Color.TRANSPARENT);
-        this.setBackground(mBackground);
+    /** @return true if this header is expanded. */
+    public boolean isExpanded() {
+        return mIsExpanded;
     }
 
-
     /** Sets the {@link WidgetsListDrawableState} and refreshes the background drawable. */
     @UiThread
     public void setListDrawableState(WidgetsListDrawableState state) {
@@ -260,12 +205,15 @@
 
     @Override
     protected int[] onCreateDrawableState(int extraSpace) {
-        if (mListDrawableState == null) return super.onCreateDrawableState(extraSpace);
-        // Augment the state set from the super implementation with the custom states from
-        // mListDrawableState.
-        int[] drawableState =
-                super.onCreateDrawableState(extraSpace + mListDrawableState.mStateSet.length);
-        mergeDrawableStates(drawableState, mListDrawableState.mStateSet);
+        // We create a drawable state with an additional two spaces to be able to fit expanded state
+        // and the list drawable state.
+        int[] drawableState = super.onCreateDrawableState(extraSpace + 2);
+        if (mIsExpanded) {
+            mergeDrawableStates(drawableState, EXPANDED_DRAWABLE_STATE);
+        }
+        if (mListDrawableState != null) {
+            mergeDrawableStates(drawableState, mListDrawableState.mStateSet);
+        }
         return drawableState;
     }
 
@@ -283,10 +231,4 @@
             }
         }
     }
-
-    /** A listener for the widget section expansion / collapse events. */
-    public interface OnExpansionChangeListener {
-        /** Notifies that the widget section is expanded or collapsed. */
-        void onExpansionChange(boolean isExpanded);
-    }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
index 27a2b1c..af4a5e6 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinder.java
@@ -32,17 +32,23 @@
         ViewHolderBinder<WidgetsListHeaderEntry, WidgetsListHeaderHolder> {
     private final LayoutInflater mLayoutInflater;
     private final OnHeaderClickListener mOnHeaderClickListener;
+    private final boolean mIsTwoPane;
 
     public WidgetsListHeaderViewHolderBinder(LayoutInflater layoutInflater,
-            OnHeaderClickListener onHeaderClickListener) {
+            OnHeaderClickListener onHeaderClickListener, boolean isTwoPane) {
         mLayoutInflater = layoutInflater;
         mOnHeaderClickListener = onHeaderClickListener;
+        mIsTwoPane = isTwoPane;
     }
 
     @Override
     public WidgetsListHeaderHolder newViewHolder(ViewGroup parent) {
         return new WidgetsListHeaderHolder((WidgetsListHeader) mLayoutInflater.inflate(
-                R.layout.widgets_list_row_header, parent, false));
+                mIsTwoPane
+                        ? R.layout.widgets_list_row_header_two_pane
+                        : R.layout.widgets_list_row_header,
+                parent,
+                false));
     }
 
     @Override
@@ -54,10 +60,11 @@
         widgetsListHeader.setListDrawableState(
                 WidgetsListDrawableState.obtain(
                         (position & POSITION_FIRST) != 0,
-                        (position & POSITION_LAST) != 0,
-                        /* isExpanded= */ data.isWidgetListShown()));
-        widgetsListHeader.setOnExpandChangeListener(isExpanded ->
-                mOnHeaderClickListener.onHeaderClicked(isExpanded,
-                        PackageUserKey.fromPackageItemInfo(data.mPkgItem)));
+                        (position & POSITION_LAST) != 0));
+        widgetsListHeader.setOnClickListener(view -> {
+            widgetsListHeader.setExpanded(mIsTwoPane || !widgetsListHeader.isExpanded());
+            mOnHeaderClickListener.onHeaderClicked(widgetsListHeader.isExpanded(),
+                    PackageUserKey.fromPackageItemInfo(data.mPkgItem));
+        });
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index 4c1f7a9..2e8f0ab 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -15,9 +15,6 @@
  */
 package com.android.launcher3.widget.picker;
 
-import static com.android.launcher3.widget.picker.WidgetsListDrawableState.LAST;
-import static com.android.launcher3.widget.picker.WidgetsListDrawableState.MIDDLE;
-
 import android.graphics.Bitmap;
 import android.util.Log;
 import android.util.Pair;
@@ -84,7 +81,11 @@
             Log.d(TAG, String.format("onBindViewHolder [widget#=%d, table.getChildCount=%d]",
                     entry.mWidgets.size(), table.getChildCount()));
         }
-        table.setListDrawableState(((position & POSITION_LAST) != 0) ? LAST : MIDDLE);
+        table.setListDrawableState(
+                WidgetsListDrawableState.obtain(
+                        (position & POSITION_FIRST) != 0,
+                        (position & POSITION_LAST) != 0));
+
         List<ArrayList<WidgetItem>> widgetItemsTable =
                 WidgetsTableUtils.groupWidgetItemsIntoTableWithReordering(
                         entry.mWidgets, entry.getMaxSpanSizeInCells());
diff --git a/tests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
index 31e8d30..e8372b1 100644
--- a/tests/src/com/android/launcher3/LauncherPrefsTest.kt
+++ b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
@@ -13,7 +13,10 @@
 private val TEST_BOOLEAN_ITEM = LauncherPrefs.nonRestorableItem("1", false)
 private val TEST_STRING_ITEM = LauncherPrefs.nonRestorableItem("2", "( ͡❛ ͜ʖ ͡❛)")
 private val TEST_INT_ITEM = LauncherPrefs.nonRestorableItem("3", -1)
-private val TEST_CONTEXTUAL_ITEM = LauncherPrefs.backedUpItem("4") { true }
+private val TEST_CONTEXTUAL_ITEM = ContextualItem("4", true, { true }, Boolean::class.java)
+
+private const val TEST_DEFAULT_VALUE = "default"
+private const val TEST_PREF_KEY = "test_pref_key"
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -151,4 +154,16 @@
     fun get_contextualItem_returnsCorrectDefault() {
         assertThat(launcherPrefs.get(TEST_CONTEXTUAL_ITEM)).isTrue()
     }
+
+    @Test
+    fun getItemSharedPrefFile_forNonRestorableItem_isCorrect() {
+        val nonRestorableItem = LauncherPrefs.nonRestorableItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE)
+        assertThat(nonRestorableItem.sharedPrefFile).isEqualTo(LauncherFiles.DEVICE_PREFERENCES_KEY)
+    }
+
+    @Test
+    fun getItemSharedPrefFile_forBackedUpItem_isCorrect() {
+        val backedUpItem = LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE)
+        assertThat(backedUpItem.sharedPrefFile).isEqualTo(LauncherFiles.SHARED_PREFERENCES_KEY)
+    }
 }
diff --git a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
new file mode 100644
index 0000000..84156e7
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.Intent
+import android.os.Process
+import android.os.UserManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+/** Unit tests for {@link LockedUserUtil} */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LockedUserStateTest {
+
+    @Mock lateinit var userManager: UserManager
+    @Mock lateinit var context: Context
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        `when`(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
+    }
+
+    @Test
+    fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() {
+        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
+        LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
+        val action: Runnable = mock()
+
+        LockedUserState.get(context).runOnUserUnlocked(action)
+        verify(action).run()
+    }
+
+    @Test
+    fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() {
+        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
+        LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
+        val action: Runnable = mock()
+
+        LockedUserState.get(context).runOnUserUnlocked(action)
+        verifyZeroInteractions(action)
+
+        LockedUserState.get(context)
+            .mUserUnlockedReceiver
+            .onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
+
+        verify(action).run()
+    }
+
+    @Test
+    fun isUserUnlocked_returns_true_when_user_is_unlocked() {
+        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
+        LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
+        assertThat(LockedUserState.get(context).isUserUnlocked).isTrue()
+    }
+
+    @Test
+    fun isUserUnlocked_returns_false_when_user_is_locked() {
+        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
+        LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
+        assertThat(LockedUserState.get(context).isUserUnlocked).isFalse()
+    }
+}
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index 612a4c6..76492ba 100644
--- a/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -90,7 +90,8 @@
         }).when(mIconCache).getTitleNoCache(any());
         mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
                 LayoutInflater.from(mContext),
-                mOnHeaderClickListener);
+                mOnHeaderClickListener,
+                false);
     }
 
     @Test
diff --git a/tests/tapl/com/android/launcher3/tapl/AllAppsQsb.java b/tests/tapl/com/android/launcher3/tapl/AllAppsQsb.java
new file mode 100644
index 0000000..0931cd4
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/AllAppsQsb.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * Operations on AllApp screen qsb.
+ */
+class AllAppsQsb extends Qsb {
+
+    private final UiObject2 mAllAppsContainer;
+
+    AllAppsQsb(LauncherInstrumentation launcher, UiObject2 allAppsContainer) {
+        super(launcher);
+        mAllAppsContainer = allAppsContainer;
+        waitForQsbObject();
+    }
+
+    @Override
+    protected UiObject2 waitForQsbObject() {
+        return mLauncher.waitForObjectInContainer(mAllAppsContainer, "search_container_all_apps");
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
index e0c4c19..44de65a 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
@@ -109,4 +109,13 @@
             }
         }
     }
+
+    /**
+     * Return the QSB UI object on the AllApps screen.
+     * @return the QSB UI object.
+     */
+    @NonNull
+    public Qsb getQsb() {
+        return new AllAppsQsb(mLauncher, verifyActiveContainer());
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeQsb.java b/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
index c365708..20d09a1 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
@@ -15,69 +15,23 @@
  */
 package com.android.launcher3.tapl;
 
-import androidx.annotation.NonNull;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
 
 /**
- * Operations on home screen qsb.
+ * Operations on Home screen qsb.
  */
-public class HomeQsb {
+class HomeQsb extends Qsb {
 
-    private final LauncherInstrumentation mLauncher;
-    private static final String ASSISTANT_APP_PACKAGE = "com.google.android.googlequicksearchbox";
-    private static final String ASSISTANT_ICON_RES_ID = "mic_icon";
+    private final UiObject2 mHotSeat;
 
-
-    HomeQsb(LauncherInstrumentation launcher) {
-        mLauncher = launcher;
-        mLauncher.waitForLauncherObject("search_container_hotseat");
+    HomeQsb(LauncherInstrumentation launcher, UiObject2 hotseat) {
+        super(launcher);
+        mHotSeat = hotseat;
+        waitForQsbObject();
     }
 
-    /**
-     * Launch assistant app by tapping mic icon on qsb.
-     */
-    @NonNull
-    public LaunchedAppState launchAssistant() {
-        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                "want to click assistant mic icon button");
-             LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-            UiObject2 assistantIcon = mLauncher.waitForLauncherObject(ASSISTANT_ICON_RES_ID);
-
-            LauncherInstrumentation.log("HomeQsb.launchAssistant before click "
-                    + assistantIcon.getVisibleCenter() + " in "
-                    + mLauncher.getVisibleBounds(assistantIcon));
-
-            mLauncher.clickLauncherObject(assistantIcon);
-
-            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
-                // assert Assistant App Launched
-                BySelector selector = By.pkg(ASSISTANT_APP_PACKAGE);
-                mLauncher.assertTrue(
-                        "assistant app didn't start: (" + selector + ")",
-                        mLauncher.getDevice().wait(Until.hasObject(selector),
-                                LauncherInstrumentation.WAIT_TIME_MS)
-                );
-                return new LaunchedAppState(mLauncher);
-            }
-        }
-    }
-
-    /**
-     * Show search result page from tapping qsb.
-     */
-    public SearchResultFromQsb showSearchResult() {
-        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                "want to open search result page");
-             LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
-            mLauncher.clickLauncherObject(
-                    mLauncher.waitForLauncherObject("search_container_hotseat"));
-            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
-                    "clicked qsb to open search result page")) {
-                return new SearchResultFromQsb(mLauncher);
-            }
-        }
+    @Override
+    protected UiObject2 waitForQsbObject() {
+        return mLauncher.waitForObjectInContainer(mHotSeat, "search_container_hotseat");
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Qsb.java b/tests/tapl/com/android/launcher3/tapl/Qsb.java
new file mode 100644
index 0000000..6bc4f21
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/Qsb.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+/**
+ * Operations on qsb from either Home screen or AllApp screen.
+ */
+public abstract class Qsb {
+
+    private static final String ASSISTANT_APP_PACKAGE = "com.google.android.googlequicksearchbox";
+    private static final String ASSISTANT_ICON_RES_ID = "mic_icon";
+    protected final LauncherInstrumentation mLauncher;
+
+    protected Qsb(LauncherInstrumentation launcher) {
+        mLauncher = launcher;
+    }
+
+    // Waits for the quick search box.
+    protected abstract UiObject2 waitForQsbObject();
+    /**
+     * Launch assistant app by tapping mic icon on qsb.
+     */
+
+    @NonNull
+    public LaunchedAppState launchAssistant() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to click assistant mic icon button");
+             LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            UiObject2 assistantIcon = mLauncher.waitForLauncherObject(ASSISTANT_ICON_RES_ID);
+
+            LauncherInstrumentation.log("Qsb.launchAssistant before click "
+                    + assistantIcon.getVisibleCenter() + " in "
+                    + mLauncher.getVisibleBounds(assistantIcon));
+
+            mLauncher.clickLauncherObject(assistantIcon);
+
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
+                // assert Assistant App Launched
+                BySelector selector = By.pkg(ASSISTANT_APP_PACKAGE);
+                mLauncher.assertTrue(
+                        "assistant app didn't start: (" + selector + ")",
+                        mLauncher.getDevice().wait(Until.hasObject(selector),
+                                LauncherInstrumentation.WAIT_TIME_MS)
+                );
+                return new LaunchedAppState(mLauncher);
+            }
+        }
+    }
+
+    /**
+     * Show search result page from tapping qsb.
+     */
+    public SearchResultFromQsb showSearchResult() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to open search result page");
+             LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            mLauncher.clickLauncherObject(waitForQsbObject());
+            // wait for the result rendering to complete
+            mLauncher.waitForIdle();
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+                    "clicked qsb to open search result page")) {
+                return new SearchResultFromQsb(mLauncher);
+            }
+        }
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index 80e4116..80176e9 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -23,7 +23,7 @@
 import java.util.ArrayList;
 
 /**
- * Operations on search result page opened from home screen qsb.
+ * Operations on search result page opened from qsb.
  */
 public class SearchResultFromQsb {
     // The input resource id in the search box.
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 0197a11..1939dc6 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -119,10 +119,11 @@
      *
      * The qsb must already be visible when calling this method.
      */
-    public HomeQsb getQsb() {
+    @NonNull
+    public Qsb getQsb() {
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                 "want to get the home qsb")) {
-            return new HomeQsb(mLauncher);
+            return new HomeQsb(mLauncher, mHotseat);
         }
     }