Merge "Prevent menu items from showing on split tasks" into main
diff --git a/OWNERS b/OWNERS
index dd2d00e..654493f 100644
--- a/OWNERS
+++ b/OWNERS
@@ -45,3 +45,6 @@
 
 per-file FeatureFlags.java, globs = set noparent
 per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
+
+per-file DeviceConfigWrapper.java, globs = set noparent
+per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com
diff --git a/quickstep/res/drawable/bg_overview_clear_all_button.xml b/quickstep/res/drawable/bg_overview_clear_all_button.xml
index f3ff6ce..143761f 100644
--- a/quickstep/res/drawable/bg_overview_clear_all_button.xml
+++ b/quickstep/res/drawable/bg_overview_clear_all_button.xml
@@ -20,7 +20,7 @@
     <item>
         <shape android:shape="rectangle"
             android:tint="?colorButtonNormal">
-            <corners android:radius="24dp" />
+            <corners android:radius="@dimen/recents_clear_all_outline_radius" />
             <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
         </shape>
     </item>
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index da94c3a..3380ea4 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -17,10 +17,12 @@
 <com.android.quickstep.views.ClearAllButton
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
     style="@style/OverviewClearAllButton"
     android:id="@+id/clear_all"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:text="@string/recents_clear_all"
     android:textColor="?androidprv:attr/materialColorOnSurface"
+    launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
     android:textSize="14sp" />
\ No newline at end of file
diff --git a/quickstep/res/values/attrs.xml b/quickstep/res/values/attrs.xml
index 7288774..ccc7f18 100644
--- a/quickstep/res/values/attrs.xml
+++ b/quickstep/res/values/attrs.xml
@@ -30,6 +30,11 @@
         <attr name="hoverBorderColor" format="color" />
     </declare-styleable>
 
+    <declare-styleable name="ClearAllButton">
+        <!-- focus border color for overview clear all button views -->
+        <attr name="focusBorderColor" />
+    </declare-styleable>
+
     <!--
          Gesture nav edu specific attributes. These attributes are used to customize Gesture nav edu
          view lottie animation colors in XML files.
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 31d4071..c4ee11a 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -38,6 +38,7 @@
     <string name="nav_handle_long_press_handler_class" translatable="false"></string>
     <string name="assist_utils_class" translatable="false"></string>
     <string name="assist_state_manager_class" translatable="false"></string>
+    <string name="api_wrapper_class" translatable="false">com.android.launcher3.uioverrides.SystemApiWrapper</string>
 
     <!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
          determines how many thumbnails will be fetched in the background. -->
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index af1ab99..00dc7cd 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -98,6 +98,8 @@
     <dimen name="default_task_dismiss_drag_velocity_grid_focus_task">5dp</dimen>
 
     <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
+    <dimen name="recents_clear_all_outline_radius">24dp</dimen>
+    <dimen name="recents_clear_all_outline_padding">2dp</dimen>
 
     <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
              loading full resolution screenshots. -->
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index eb9c5f0..71855eb 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -26,6 +26,8 @@
     <string name="recent_task_option_pin">Pin</string>
     <!-- Title for an option to enter freeform mode for a given app -->
     <string name="recent_task_option_freeform">Freeform</string>
+    <!-- Title for an option to enter desktop windowing mode for a given app -->
+    <string name="recent_task_option_desktop">Desktop</string>
 
     <!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
     <string name="recents_empty_message">No recent items</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index be532b4..0697f47 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -173,6 +173,7 @@
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map.Entry;
 
 /**
  * Manages the opening and closing app transitions from Launcher
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 3e9272d..70e01f5 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -240,7 +240,11 @@
             icon.reset();
             if (predictionCount > i) {
                 icon.setVisibility(View.VISIBLE);
-                icon.applyFromWorkspaceItem(mPredictedApps.get(i));
+                WorkspaceItemInfo predictedItem = mPredictedApps.get(i);
+                predictedItem.rank = i;
+                predictedItem.cellX = i;
+                predictedItem.cellY = 0;
+                icon.applyFromWorkspaceItem(predictedItem);
             } else {
                 icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE);
             }
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index 10733fb..64fe30c 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -56,6 +56,11 @@
         systemUiProxy.showDesktopApps(desktopTaskView.display.displayId, transition)
     }
 
+    /** Launch desktop tasks from recents view */
+    fun moveToDesktop(taskId: Int) {
+        systemUiProxy.moveToDesktop(taskId)
+    }
+
     private class RemoteDesktopLaunchTransitionRunner(
         private val desktopTaskView: DesktopTaskView,
         private val stateManager: StateManager<*>,
@@ -99,8 +104,7 @@
             finishCallback: IRemoteTransitionFinishedCallback
         ) {}
 
-        override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {
-        }
+        override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {}
     }
 
     companion object {
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 672bd1d..1c5a75d 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -499,7 +499,7 @@
 
         @Override
         public void onClick(View view) {
-            dismissTaskMenuView(mTarget);
+            dismissTaskMenuView();
             pinPrediction(mItemInfo);
         }
     }
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index 176091a..c0dacf1 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -48,7 +48,7 @@
             "persist.wm.debug.desktop_stashing", false);
     private final Launcher mLauncher;
 
-    private int mVisibleFreeformTasksCount;
+    private int mVisibleDesktopTasksCount;
     private boolean mInOverviewState;
     private boolean mBackgroundStateEnabled;
     private boolean mGestureInProgress;
@@ -73,7 +73,7 @@
                         if (DEBUG) {
                             Log.d(TAG, "desktop visible tasks count changed=" + visibleTasksCount);
                         }
-                        setVisibleFreeformTasksCount(visibleTasksCount);
+                        setVisibleDesktopTasksCount(visibleTasksCount);
                     }
                 });
             }
@@ -108,51 +108,51 @@
     }
 
     /**
-     * Whether freeform windows are visible in desktop mode.
+     * Whether desktop tasks are visible in desktop mode.
      */
-    public boolean areFreeformTasksVisible() {
-        boolean freeformTasksVisible = mVisibleFreeformTasksCount > 0;
+    public boolean areDesktopTasksVisible() {
+        boolean desktopTasksVisible = mVisibleDesktopTasksCount > 0;
         if (DEBUG) {
-            Log.d(TAG, "areFreeformTasksVisible: freeformVisible=" + freeformTasksVisible
+            Log.d(TAG, "areDesktopTasksVisible: desktopVisible=" + desktopTasksVisible
                     + " overview=" + mInOverviewState);
         }
-        return freeformTasksVisible && !mInOverviewState;
+        return desktopTasksVisible && !mInOverviewState;
     }
 
     /**
-     * Number of visible freeform windows in desktop mode.
+     * Number of visible desktop windows in desktop mode.
      */
-    public int getVisibleFreeformTasksCount() {
-        return mVisibleFreeformTasksCount;
+    public int getVisibleDesktopTasksCount() {
+        return mVisibleDesktopTasksCount;
     }
 
     /**
-     * Sets the number of freeform windows that are visible and updates launcher visibility based on
+     * Sets the number of desktop windows that are visible and updates launcher visibility based on
      * it.
      */
-    public void setVisibleFreeformTasksCount(int visibleTasksCount) {
+    public void setVisibleDesktopTasksCount(int visibleTasksCount) {
         if (DEBUG) {
-            Log.d(TAG, "setVisibleFreeformTasksCount: visibleTasksCount=" + visibleTasksCount
-                    + " currentValue=" + mVisibleFreeformTasksCount);
+            Log.d(TAG, "setVisibleDesktopTasksCount: visibleTasksCount=" + visibleTasksCount
+                    + " currentValue=" + mVisibleDesktopTasksCount);
         }
 
-        if (visibleTasksCount != mVisibleFreeformTasksCount) {
-            final boolean wasVisible = mVisibleFreeformTasksCount > 0;
+        if (visibleTasksCount != mVisibleDesktopTasksCount) {
+            final boolean wasVisible = mVisibleDesktopTasksCount > 0;
             final boolean isVisible = visibleTasksCount > 0;
-            mVisibleFreeformTasksCount = visibleTasksCount;
+            mVisibleDesktopTasksCount = visibleTasksCount;
 
             if (wasVisible != isVisible) {
-                if (mVisibleFreeformTasksCount > 0) {
+                if (mVisibleDesktopTasksCount > 0) {
                     setLauncherViewsVisibility(View.INVISIBLE);
                     if (!mInOverviewState) {
-                        // When freeform is visible & we're not in overview, we want launcher to
-                        // appear paused, this ensures that taskbar displays.
+                        // When desktop tasks are visible & we're not in overview, we want launcher
+                        // to appear paused, this ensures that taskbar displays.
                         markLauncherPaused();
                     }
                 } else {
                     setLauncherViewsVisibility(View.VISIBLE);
-                    // If freeform isn't visible ensure that launcher appears resumed to behave
-                    // normally.
+                    // If desktop tasks aren't visible, ensure that launcher appears resumed to
+                    // behave normally.
                     markLauncherResumed();
                 }
             }
@@ -181,9 +181,9 @@
             if (mInOverviewState) {
                 setLauncherViewsVisibility(View.VISIBLE);
                 markLauncherResumed();
-            } else if (areFreeformTasksVisible() && !mGestureInProgress) {
+            } else if (areDesktopTasksVisible() && !mGestureInProgress) {
                 // Switching out of overview state and gesture finished.
-                // If freeform tasks are still visible, hide launcher again.
+                // If desktop tasks are still visible, hide launcher again.
                 setLauncherViewsVisibility(View.INVISIBLE);
                 markLauncherPaused();
             }
@@ -200,8 +200,8 @@
             if (mBackgroundStateEnabled) {
                 setLauncherViewsVisibility(View.VISIBLE);
                 markLauncherResumed();
-            } else if (areFreeformTasksVisible() && !mGestureInProgress) {
-                // Switching out of background state. If freeform tasks are visible, pause launcher.
+            } else if (areDesktopTasksVisible() && !mGestureInProgress) {
+                // Switching out of background state. If desktop tasks are visible, pause launcher.
                 setLauncherViewsVisibility(View.INVISIBLE);
                 markLauncherPaused();
             }
@@ -251,7 +251,7 @@
      * Handle launcher moving to home due to home gesture or home button press.
      */
     public void onHomeActionTriggered() {
-        if (IS_STASHING_ENABLED && areFreeformTasksVisible()) {
+        if (IS_STASHING_ENABLED && areDesktopTasksVisible()) {
             SystemUiProxy.INSTANCE.get(mLauncher).stashDesktopApps(mLauncher.getDisplayId());
         }
     }
@@ -270,7 +270,7 @@
             dragLayer.setVisibility(visibility);
         }
         if (mLauncher instanceof QuickstepLauncher ql && ql.getTaskbarUIController() != null
-                && mVisibleFreeformTasksCount != 0) {
+                && mVisibleDesktopTasksCount != 0) {
             ql.getTaskbarUIController().onLauncherVisibilityChanged(visibility == VISIBLE);
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index d89f49b..584106b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -115,7 +115,7 @@
         DesktopVisibilityController desktopController =
                 LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
         final boolean onDesktop =
-                desktopController != null && desktopController.areFreeformTasksVisible();
+                desktopController != null && desktopController.areDesktopTasksVisible();
 
         if (mModel.isTaskListValid(mTaskListChangeId)) {
             // When we are opening the KQS with no focus override, check if the first task is
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 23380d6..03c2805 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -219,7 +219,7 @@
         DesktopVisibilityController desktopController =
                 LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
         final boolean onDesktop =
-                desktopController != null && desktopController.areFreeformTasksVisible();
+                desktopController != null && desktopController.areDesktopTasksVisible();
         if (onDesktop) {
             isVisible = false;
         }
@@ -425,15 +425,15 @@
     }
 
     @Override
-    protected boolean isInOverview() {
-        return mTaskbarLauncherStateController.isInOverview();
+    protected boolean isInOverviewUi() {
+        return mTaskbarLauncherStateController.isInOverviewUi();
     }
 
     @Override
     protected boolean canToggleHomeAllApps() {
         return mLauncher.isResumed()
-                && !mTaskbarLauncherStateController.isInOverview()
-                && !mLauncher.areFreeformTasksVisible();
+                && !mTaskbarLauncherStateController.isInOverviewUi()
+                && !mLauncher.areDesktopTasksVisible();
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 390dec9..49d4afe 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -113,8 +113,8 @@
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
-import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.ActivityOptionsWrapper;
+import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Executors;
@@ -665,7 +665,7 @@
 
         LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
                 LauncherAtom.TaskBarContainer.newBuilder();
-        if (mControllers.uiController.isInOverview()) {
+        if (mControllers.uiController.isInOverviewUi()) {
             taskbarBuilder.setTaskSwitcherContainer(
                     LauncherAtom.TaskSwitcherContainer.newBuilder());
         }
@@ -1120,7 +1120,7 @@
                         } else if (info.isPromise()) {
                             TestLogging.recordEvent(
                                     TestProtocol.SEQUENCE_MAIN, "start: taskbarPromiseIcon");
-                            intent = ApiWrapper.getAppMarketActivityIntent(this,
+                            intent = ApiWrapper.INSTANCE.get(this).getAppMarketActivityIntent(
                                     info.getTargetPackage(), Process.myUserHandle());
                             startActivity(intent);
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 8dc81cf..6163dad 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -375,7 +375,7 @@
         ) {
             // Taskbar has some touchable elements, take over the full taskbar area
             if (
-                controllers.uiController.isInOverview &&
+                controllers.uiController.isInOverviewUi &&
                     DisplayController.isTransientTaskbar(context)
             ) {
                 val region =
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index b0abbe9..17dcace 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -666,8 +666,8 @@
                 && !mLauncher.getWorkspace().isOverlayShown();
     }
 
-    boolean isInOverview() {
-        return mLauncherState == LauncherState.OVERVIEW;
+    boolean isInOverviewUi() {
+        return mLauncherState.overviewUi;
     }
 
     private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index e4f9ba5..e47640b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -58,6 +58,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.statemanager.StatefulActivity;
@@ -339,12 +340,12 @@
 
     /**
      * Toggles All Apps for Taskbar or Launcher depending on the current state.
-     *
-     * @param homeAllAppsIntent Intent used if Taskbar is not enabled or Launcher is resumed.
      */
-    public void toggleAllApps(Intent homeAllAppsIntent) {
+    public void toggleAllApps() {
         if (mTaskbarActivityContext == null || mTaskbarActivityContext.canToggleHomeAllApps()) {
-            mContext.startActivity(homeAllAppsIntent);
+            // Home All Apps should be toggled from this class, because the controllers are not
+            // initialized when Taskbar is disabled (i.e. TaskbarActivityContext is null).
+            if (mActivity instanceof Launcher l) l.toggleAllAppsSearch();
         } else {
             mTaskbarActivityContext.toggleAllAppsSearch();
         }
@@ -443,7 +444,8 @@
                 LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
 
             // All Apps action is unrelated to navbar unification, so we only need to check DP.
-            mAllAppsActionManager.setTaskbarPresent(dp != null && dp.isTaskbarPresent);
+            final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
+            mAllAppsActionManager.setTaskbarPresent(isLargeScreenTaskbar);
 
             destroyExistingTaskbar();
 
@@ -467,6 +469,7 @@
             }
             mSharedState.startTaskbarVariantIsTransient =
                     DisplayController.isTransientTaskbar(mTaskbarActivityContext);
+            mSharedState.allAppsVisible = mSharedState.allAppsVisible && isLargeScreenTaskbar;
             mTaskbarActivityContext.init(mSharedState);
 
             if (mActivity != null) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 7e74c27..15be8e7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -985,7 +985,7 @@
         DesktopVisibilityController visibilityController =
                 LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
         if (visibilityController != null && mActivity.isHardwareKeyboard()
-                && mActivity.isThreeButtonNav() && visibilityController.areFreeformTasksVisible()) {
+                && mActivity.isThreeButtonNav() && visibilityController.areDesktopTasksVisible()) {
             return false;
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index cb0fa40..2e78489 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -193,7 +193,7 @@
     }
 
     /** Returns {@code true} if Taskbar is currently within overview. */
-    protected boolean isInOverview() {
+    protected boolean isInOverviewUi() {
         return false;
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 99937f8..6ceec3e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -96,7 +96,7 @@
         mAllAppsCallbacks.onAllAppsTransitionStart(true);
         if (!animate) {
             mAllAppsCallbacks.onAllAppsTransitionEnd(true);
-            mTranslationShift = TRANSLATION_SHIFT_OPENED;
+            setTranslationShift(TRANSLATION_SHIFT_OPENED);
             return;
         }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
deleted file mode 100644
index 873dea8..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides;
-
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
-import android.app.Person;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ActivityInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.LauncherUserInfo;
-import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.ColorDrawable;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.ArrayMap;
-import android.window.RemoteTransition;
-
-import com.android.launcher3.Flags;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.proxy.ProxyActivityStarter;
-import com.android.launcher3.util.StartActivityParams;
-import com.android.launcher3.util.UserIconInfo;
-import com.android.quickstep.util.FadeOutRemoteTransition;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A wrapper for the hidden API calls
- */
-public class ApiWrapper {
-
-    public static final boolean TASKBAR_DRAWN_IN_PROCESS = true;
-
-    public static Person[] getPersons(ShortcutInfo si) {
-        Person[] persons = si.getPersons();
-        return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
-    }
-
-    public static Map<String, LauncherActivityInfo> getActivityOverrides(Context context) {
-        return context.getSystemService(LauncherApps.class).getActivityOverrides();
-    }
-
-    /**
-     * Creates an ActivityOptions to play fade-out animation on closing targets
-     */
-    public static ActivityOptions createFadeOutAnimOptions(Context context) {
-        ActivityOptions options = ActivityOptions.makeBasic();
-        options.setRemoteTransition(new RemoteTransition(new FadeOutRemoteTransition()));
-        return options;
-    }
-
-    /**
-     * Returns a map of all users on the device to their corresponding UI properties
-     */
-    public static Map<UserHandle, UserIconInfo> queryAllUsers(Context context) {
-        UserManager um = context.getSystemService(UserManager.class);
-        Map<UserHandle, UserIconInfo> users = new ArrayMap<>();
-        List<UserHandle> usersActual = um.getUserProfiles();
-        if (usersActual != null) {
-            for (UserHandle user : usersActual) {
-                if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpace()) {
-                    LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
-                    LauncherUserInfo launcherUserInfo = launcherApps.getLauncherUserInfo(user);
-                    if (launcherUserInfo == null) {
-                        continue;
-                    }
-                    // UserTypes not supported in Launcher are deemed to be the current
-                    // Foreground User.
-                    int userType = switch (launcherUserInfo.getUserType()) {
-                        case UserManager.USER_TYPE_PROFILE_MANAGED -> UserIconInfo.TYPE_WORK;
-                        case UserManager.USER_TYPE_PROFILE_CLONE -> UserIconInfo.TYPE_CLONED;
-                        case UserManager.USER_TYPE_PROFILE_PRIVATE -> UserIconInfo.TYPE_PRIVATE;
-                        default -> UserIconInfo.TYPE_MAIN;
-                    };
-                    long serial = launcherUserInfo.getUserSerialNumber();
-                    users.put(user, new UserIconInfo(user, userType, serial));
-                } else {
-                    long serial = um.getSerialNumberForUser(user);
-
-                    // Simple check to check if the provided user is work profile
-                    // TODO: Migrate to a better platform API
-                    NoopDrawable d = new NoopDrawable();
-                    boolean isWork = (d != context.getPackageManager().getUserBadgedIcon(d, user));
-                    UserIconInfo info = new UserIconInfo(
-                            user,
-                            isWork ? UserIconInfo.TYPE_WORK : UserIconInfo.TYPE_MAIN,
-                            serial);
-                    users.put(user, info);
-                }
-            }
-        }
-        return users;
-    }
-
-    /**
-     * Returns the list of the system packages that are installed at user creation.
-     * An empty list denotes that all system packages are installed for that user at creation.
-     */
-    public static List<String> getPreInstalledSystemPackages(Context context, UserHandle user) {
-        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
-        if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpace()
-                && Flags.privateSpaceSysAppsSeparation()) {
-            return launcherApps.getPreInstalledSystemPackages(user);
-        } else {
-            return new ArrayList<>();
-        }
-    }
-
-    /**
-     * Returns an intent which can be used to start the App Market activity (Installer
-     * Activity).
-     */
-    public static Intent getAppMarketActivityIntent(Context context, String packageName,
-            UserHandle user) {
-        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
-        if (android.os.Flags.allowPrivateProfile()
-                && Flags.enablePrivateSpace()
-                && (Flags.privateSpaceAppInstallerButton()
-                        || Flags.enablePrivateSpaceInstallShortcut())) {
-            StartActivityParams params = new StartActivityParams((PendingIntent) null, 0);
-            params.intentSender = launcherApps.getAppMarketActivityIntent(packageName, user);
-            ActivityOptions options = ActivityOptions.makeBasic()
-                    .setPendingIntentBackgroundActivityStartMode(ActivityOptions
-                            .MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-            params.options = options.toBundle();
-            params.requireActivityResult = false;
-            return ProxyActivityStarter.getLaunchIntent(context, params);
-        } else {
-            return new Intent(Intent.ACTION_VIEW)
-                    .setData(new Uri.Builder()
-                            .scheme("market")
-                            .authority("details")
-                            .appendQueryParameter("id", packageName)
-                            .build())
-                    .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app")
-                            .authority(context.getPackageName()).build());
-        }
-    }
-
-    /**
-     * Returns an intent which can be used to open Private Space Settings.
-     */
-    public static Intent getPrivateSpaceSettingsIntent(Context context) {
-        if (android.os.Flags.allowPrivateProfile() && Flags.enablePrivateSpace()) {
-            LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
-            IntentSender intentSender = launcherApps.getPrivateSpaceSettingsIntent();
-            if (intentSender == null) {
-                return null;
-            }
-            StartActivityParams params = new StartActivityParams((PendingIntent) null, 0);
-            params.intentSender = intentSender;
-            ActivityOptions options = ActivityOptions.makeBasic()
-                    .setPendingIntentBackgroundActivityStartMode(ActivityOptions
-                            .MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-            params.options = options.toBundle();
-            params.requireActivityResult = false;
-            return ProxyActivityStarter.getLaunchIntent(context, params);
-        }
-        return null;
-    }
-
-    /**
-     * Checks if an activity is flagged as non-resizeable.
-     */
-    public static boolean isNonResizeableActivity(LauncherActivityInfo lai) {
-        return lai.getActivityInfo().resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
-    }
-
-    private static class NoopDrawable extends ColorDrawable {
-        @Override
-        public int getIntrinsicHeight() {
-            return 1;
-        }
-
-        @Override
-        public int getIntrinsicWidth() {
-            return 1;
-        }
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index c5c0092..6013797 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -937,7 +937,7 @@
     @Override
     public void setResumed() {
         if (mDesktopVisibilityController != null
-                && mDesktopVisibilityController.areFreeformTasksVisible()
+                && mDesktopVisibilityController.areDesktopTasksVisible()
                 && !mDesktopVisibilityController.isRecentsGestureInProgress()) {
             // Return early to skip setting activity to appear as resumed
             // TODO(b/255649902): shouldn't be needed when we have a separate launcher state
@@ -1284,9 +1284,9 @@
     }
 
     @Override
-    public boolean areFreeformTasksVisible() {
+    public boolean areDesktopTasksVisible() {
         if (mDesktopVisibilityController != null) {
-            return mDesktopVisibilityController.areFreeformTasksVisible();
+            return mDesktopVisibilityController.areDesktopTasksVisible();
         }
         return false;
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
new file mode 100644
index 0000000..535b4c2
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides
+
+import android.app.ActivityOptions
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.LauncherActivityInfo
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.os.Flags.allowPrivateProfile
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.ArrayMap
+import android.window.RemoteTransition
+import com.android.launcher3.Flags.enablePrivateSpace
+import com.android.launcher3.Flags.enablePrivateSpaceInstallShortcut
+import com.android.launcher3.Flags.privateSpaceAppInstallerButton
+import com.android.launcher3.Flags.privateSpaceSysAppsSeparation
+import com.android.launcher3.Utilities
+import com.android.launcher3.proxy.ProxyActivityStarter
+import com.android.launcher3.util.ApiWrapper
+import com.android.launcher3.util.StartActivityParams
+import com.android.launcher3.util.UserIconInfo
+import com.android.quickstep.util.FadeOutRemoteTransition
+
+/** A wrapper for the hidden API calls */
+class SystemApiWrapper(context: Context?) : ApiWrapper(context) {
+
+    override fun getPersons(si: ShortcutInfo) = si.persons ?: Utilities.EMPTY_PERSON_ARRAY
+
+    override fun getActivityOverrides(): Map<String, LauncherActivityInfo> =
+        mContext.getSystemService(LauncherApps::class.java)!!.activityOverrides
+
+    override fun createFadeOutAnimOptions(): ActivityOptions =
+        ActivityOptions.makeBasic().apply {
+            remoteTransition = RemoteTransition(FadeOutRemoteTransition())
+        }
+
+    override fun queryAllUsers(): Map<UserHandle, UserIconInfo> {
+        if (!allowPrivateProfile() || !enablePrivateSpace()) {
+            return super.queryAllUsers()
+        }
+        val users = ArrayMap<UserHandle, UserIconInfo>()
+        mContext.getSystemService(UserManager::class.java)!!.userProfiles?.forEach { user ->
+            mContext.getSystemService(LauncherApps::class.java)!!.getLauncherUserInfo(user)?.apply {
+                users[user] =
+                    UserIconInfo(
+                        user,
+                        when (userType) {
+                            UserManager.USER_TYPE_PROFILE_MANAGED -> UserIconInfo.TYPE_WORK
+                            UserManager.USER_TYPE_PROFILE_CLONE -> UserIconInfo.TYPE_CLONED
+                            UserManager.USER_TYPE_PROFILE_PRIVATE -> UserIconInfo.TYPE_PRIVATE
+                            else -> UserIconInfo.TYPE_MAIN
+                        },
+                        userSerialNumber.toLong()
+                    )
+            }
+        }
+        return users
+    }
+
+    override fun getPreInstalledSystemPackages(user: UserHandle): List<String> =
+        if (allowPrivateProfile() && enablePrivateSpace() && privateSpaceSysAppsSeparation())
+            mContext
+                .getSystemService(LauncherApps::class.java)!!
+                .getPreInstalledSystemPackages(user)
+        else ArrayList()
+
+    override fun getAppMarketActivityIntent(packageName: String, user: UserHandle): Intent =
+        if (
+            allowPrivateProfile() &&
+                enablePrivateSpace() &&
+                (privateSpaceAppInstallerButton() || enablePrivateSpaceInstallShortcut())
+        )
+            ProxyActivityStarter.getLaunchIntent(
+                mContext,
+                StartActivityParams(null as PendingIntent?, 0).apply {
+                    intentSender =
+                        mContext
+                            .getSystemService(LauncherApps::class.java)!!
+                            .getAppMarketActivityIntent(packageName, user)
+                    options =
+                        ActivityOptions.makeBasic()
+                            .setPendingIntentBackgroundActivityStartMode(
+                                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                            )
+                            .toBundle()
+                    requireActivityResult = false
+                }
+            )
+        else super.getAppMarketActivityIntent(packageName, user)
+
+    /** Returns an intent which can be used to open Private Space Settings. */
+    override fun getPrivateSpaceSettingsIntent(): Intent? =
+        if (allowPrivateProfile() && enablePrivateSpace())
+            ProxyActivityStarter.getLaunchIntent(
+                mContext,
+                StartActivityParams(null as PendingIntent?, 0).apply {
+                    intentSender =
+                        mContext
+                            .getSystemService(LauncherApps::class.java)
+                            ?.privateSpaceSettingsIntent
+                            ?: return null
+                    options =
+                        ActivityOptions.makeBasic()
+                            .setPendingIntentBackgroundActivityStartMode(
+                                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                            )
+                            .toBundle()
+                    requireActivityResult = false
+                }
+            )
+        else null
+
+    override fun isNonResizeableActivity(lai: LauncherActivityInfo) =
+        lai.activityInfo.resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
new file mode 100644
index 0000000..6b44ca7
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides.flags
+
+import android.os.Handler
+import android.provider.DeviceConfig
+import android.text.Html
+import android.view.inputmethod.EditorInfo
+import androidx.preference.Preference
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceViewHolder
+import androidx.preference.SwitchPreference
+import com.android.launcher3.ExtendedEditText
+import com.android.launcher3.R
+import com.android.quickstep.util.DeviceConfigHelper
+import com.android.quickstep.util.DeviceConfigHelper.Companion.NAMESPACE_LAUNCHER
+import com.android.quickstep.util.DeviceConfigHelper.DebugInfo
+
+/** Helper class to generate UI for Device Config */
+class DevOptionsUiHelper {
+
+    /** Inflates preferences for all server flags in the provider PreferenceGroup */
+    fun inflateServerFlags(parent: PreferenceGroup) {
+        val prefs = DeviceConfigHelper.prefs
+        // Sort the keys in the order of modified first followed by natural order
+        val allProps =
+            DeviceConfigHelper.allProps.values
+                .toList()
+                .sortedWith(
+                    Comparator.comparingInt { prop: DebugInfo<*> ->
+                            if (prefs.contains(prop.key)) 0 else 1
+                        }
+                        .thenComparing { prop: DebugInfo<*> -> prop.key }
+                )
+
+        // First add boolean flags
+        allProps.forEach {
+            if (it.isInt) return@forEach
+            val info = it as DebugInfo<Boolean>
+
+            val preference =
+                object : SwitchPreference(parent.context) {
+                    override fun onBindViewHolder(holder: PreferenceViewHolder) {
+                        super.onBindViewHolder(holder)
+                        holder.itemView.setOnLongClickListener {
+                            prefs.edit().remove(key).apply()
+                            setChecked(info.getBoolValue())
+                            summary = info.getSummary()
+                            true
+                        }
+                    }
+                }
+            preference.key = info.key
+            preference.isPersistent = false
+            preference.title = info.key
+            preference.summary = info.getSummary()
+            preference.setChecked(prefs.getBoolean(info.key, info.getBoolValue()))
+            preference.setOnPreferenceChangeListener { _, newVal ->
+                DeviceConfigHelper.prefs.edit().putBoolean(info.key, newVal as Boolean).apply()
+                preference.summary = info.getSummary()
+                true
+            }
+            parent.addPreference(preference)
+        }
+
+        // Apply Int flags
+        allProps.forEach {
+            if (!it.isInt) return@forEach
+            val info = it as DebugInfo<Int>
+
+            val preference =
+                object : Preference(parent.context) {
+                    override fun onBindViewHolder(holder: PreferenceViewHolder) {
+                        super.onBindViewHolder(holder)
+                        val textView = holder.findViewById(R.id.pref_edit_text) as ExtendedEditText
+                        textView.setText(info.getIntValueAsString())
+                        textView.setOnEditorActionListener { _, actionId, _ ->
+                            if (actionId == EditorInfo.IME_ACTION_DONE) {
+                                DeviceConfigHelper.prefs
+                                    .edit()
+                                    .putInt(key, textView.text.toString().toInt())
+                                    .apply()
+                                Handler().post { summary = info.getSummary() }
+                                true
+                            }
+                            false
+                        }
+                        textView.setOnBackKeyListener {
+                            textView.setText(info.getIntValueAsString())
+                            true
+                        }
+
+                        holder.itemView.setOnLongClickListener {
+                            prefs.edit().remove(key).apply()
+                            textView.setText(info.getIntValueAsString())
+                            summary = info.getSummary()
+                            true
+                        }
+                    }
+                }
+            preference.key = info.key
+            preference.isPersistent = false
+            preference.title = info.key
+            preference.summary = info.getSummary()
+            preference.widgetLayoutResource = R.layout.develop_options_edit_text
+            parent.addPreference(preference)
+        }
+    }
+
+    /**
+     * Returns the summary to show the description and whether the flag overrides the default value.
+     */
+    private fun DebugInfo<*>.getSummary() =
+        Html.fromHtml(
+            (if (DeviceConfigHelper.prefs.contains(this.key))
+                "<font color='red'><b>[OVERRIDDEN]</b></font><br>"
+            else "") + this.desc
+        )
+
+    private fun DebugInfo<Boolean>.getBoolValue() =
+        DeviceConfigHelper.prefs.getBoolean(
+            this.key,
+            DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, this.key, this.valueInCode)
+        )
+
+    private fun DebugInfo<Int>.getIntValueAsString() =
+        DeviceConfigHelper.prefs
+            .getInt(this.key, DeviceConfig.getInt(NAMESPACE_LAUNCHER, this.key, this.valueInCode))
+            .toString()
+
+    companion object {
+        const val TAG = "DeviceConfigUIHelper"
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
index 6713964..fd6bf4d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
@@ -20,19 +20,8 @@
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
-import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
-import static com.android.launcher3.LauncherPrefs.PRIVATE_SPACE_APPS;
-import static com.android.launcher3.config.FeatureFlags.LPNH_EXTRA_TOUCH_WIDTH_DP;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_DELAY;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_END_SCALE_PERCENT;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_ITERATIONS;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_SCALE_EXPONENT;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_START_SCALE_PERCENT;
-import static com.android.launcher3.config.FeatureFlags.LPNH_SLOP_PERCENTAGE;
-import static com.android.launcher3.config.FeatureFlags.LPNH_TIMEOUT_MS;
 import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_HIGHLIGHT_KEY;
 import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
-import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
 import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
 import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_COUNT;
 import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN;
@@ -51,7 +40,6 @@
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.util.ArrayMap;
-import android.util.Log;
 import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -61,21 +49,18 @@
 
 import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceDataStore;
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceScreen;
 import androidx.preference.PreferenceViewHolder;
-import androidx.preference.SeekBarPreference;
 import androidx.preference.SwitchPreference;
 
-import com.android.launcher3.ConstantItem;
-import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.systemui.shared.plugins.PluginEnabler;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -96,8 +81,6 @@
     private final PreferenceFragmentCompat mFragment;
     private final PreferenceScreen mPreferenceScreen;
 
-    private final FlagTogglerPrefUi mFlagTogglerPrefUi;
-
     private PreferenceCategory mPluginsCategory;
 
     public DeveloperOptionsUI(PreferenceFragmentCompat fragment, PreferenceCategory flags) {
@@ -112,20 +95,14 @@
         parent.addView(topBar, parent.indexOfChild(listView));
         initSearch(topBar.findViewById(R.id.filter_box));
 
-        mFlagTogglerPrefUi = new FlagTogglerPrefUi(mFragment.requireActivity(),
-                topBar.findViewById(R.id.flag_apply_btn));
-        mFlagTogglerPrefUi.applyTo(flags);
+        new FlagTogglerPrefUi(mFragment.requireActivity(), topBar.findViewById(R.id.flag_apply_btn))
+                .applyTo(flags);
+        DevOptionsUiHelper uiHelper = new DevOptionsUiHelper();
+        uiHelper.inflateServerFlags(newCategory("Server flags"));
 
         loadPluginPrefs();
         maybeAddSandboxCategory();
         addOnboardingPrefsCatergory();
-        if (FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()) {
-            addAllAppsFromOverviewCatergory();
-        }
-        addCustomLpnhCategory();
-        if (Flags.enablePrivateSpace()) {
-            addCustomPrivateAppsCategory();
-        }
     }
 
     private void filterPreferences(String query, PreferenceGroup pg) {
@@ -205,7 +182,7 @@
                 new ArrayMap<>();
 
         Set<String> pluginPermissionApps = pm.getPackagesHoldingPermissions(
-                new String[]{PLUGIN_PERMISSION}, MATCH_DISABLED_COMPONENTS)
+                        new String[]{PLUGIN_PERMISSION}, MATCH_DISABLED_COMPONENTS)
                 .stream()
                 .map(pi -> pi.packageName)
                 .collect(Collectors.toSet());
@@ -228,7 +205,7 @@
             }
         }
 
-        PreferenceDataStore enabler = manager.getPluginEnabler();
+        PluginEnabler enabler = manager.getPluginEnabler();
         plugins.forEach((key, si) -> {
             String packageName = key.first;
             List<ComponentName> componentNames = si.stream()
@@ -347,111 +324,10 @@
         return onboardingPref;
     }
 
-    private void addAllAppsFromOverviewCatergory() {
-        PreferenceCategory category = newCategory("All Apps from Overview Config");
-        category.addPreference(createSeekBarPreference("Threshold to open All Apps from Overview",
-                105, 500, 100, ALL_APPS_OVERVIEW_THRESHOLD));
-    }
-
-    private void addCustomLpnhCategory() {
-        PreferenceCategory category = newCategory("Long Press Nav Handle Config");
-        if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
-            category.addPreference(createSeekBarPreference(
-                    "Slop multiplier (applied to edge slop, "
-                            + "which is generally already 50% higher than touch slop)",
-                    25, 200, 100, LPNH_SLOP_PERCENTAGE));
-            category.addPreference(createSeekBarPreference(
-                    "Extra width DP (how far outside the sides of the nav bar to trigger)",
-                    // Stashed taskbar is currently 220dp; -86 (x2) would result in 48dp touch area.
-                    -86, 100, 1, LPNH_EXTRA_TOUCH_WIDTH_DP));
-            category.addPreference(createSeekBarPreference("LPNH timeout",
-                    100, 500, 1, LPNH_TIMEOUT_MS));
-        }
-        if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get()) {
-            category.addPreference(
-                    createSeekBarPreference("Haptic hint start scale",
-                            0, 100, 100, LPNH_HAPTIC_HINT_START_SCALE_PERCENT));
-            category.addPreference(createSeekBarPreference("Haptic hint end scale",
-                    0, 100, 100, LPNH_HAPTIC_HINT_END_SCALE_PERCENT));
-            category.addPreference(
-                    createSeekBarPreference("Haptic hint scale exponent",
-                            1, 5, 1, LPNH_HAPTIC_HINT_SCALE_EXPONENT));
-            category.addPreference(
-                    createSeekBarPreference("Haptic hint iterations (12 ms each)",
-                            0, 200, 1, LPNH_HAPTIC_HINT_ITERATIONS));
-            category.addPreference(createSeekBarPreference("Haptic hint delay (ms)",
-                    0, 400, 1, LPNH_HAPTIC_HINT_DELAY));
-        }
-    }
-
-    private void addCustomPrivateAppsCategory() {
-        PreferenceCategory category = newCategory("Apps in Private Space Config");
-        category.addPreference(createSeekBarPreference(
-                "Number of Apps to put in private region", 0, 100, 1, PRIVATE_SPACE_APPS));
-    }
-
-    private SeekBarPreference createSeekBarPreference(String title, int min,
-            int max, int scale, FeatureFlags.IntFlag flag) {
-        if (!(flag instanceof IntDebugFlag)) {
-            Log.e(TAG, "Cannot create seekbar preference with IntFlag. Use a launcher preference "
-                    + "flag or pref-backed IntDebugFlag instead");
-            return null;
-        }
-        IntDebugFlag debugflag = (IntDebugFlag) flag;
-        if (debugflag.launcherPrefFlag == null) {
-            Log.e(TAG, "Cannot create seekbar preference with IntDebugFlag. Use a launcher "
-                    + "preference flag or pref-backed IntDebugFlag instead");
-            return null;
-        }
-        SeekBarPreference seekBarPref = createSeekBarPreference(title, min, max, scale,
-                debugflag.launcherPrefFlag);
-        int value = flag.get();
-        seekBarPref.setValue(value);
-        // For some reason the initial value is not triggering the summary update, so call manually.
-        seekBarPref.setSummary(String.valueOf(scale == 1 ? value
-                : value / (float) scale));
-        return seekBarPref;
-    }
-
-    /**
-     * Create a preference with text and a seek bar. Should be added to a PreferenceCategory.
-     *
-     * @param title text to show for this seek bar
-     * @param min min value for the seek bar
-     * @param max max value for the seek bar
-     * @param scale how much to divide the value to convert int to float
-     * @param launcherPref used to store the current value
-     */
-    private SeekBarPreference createSeekBarPreference(String title, int min, int max, int scale,
-            ConstantItem<Integer> launcherPref) {
-        SeekBarPreference seekBarPref = new SeekBarPreference(getContext());
-        seekBarPref.setTitle(title);
-        seekBarPref.setSingleLineTitle(false);
-
-        seekBarPref.setMax(max);
-        seekBarPref.setMin(min);
-        seekBarPref.setUpdatesContinuously(true);
-        seekBarPref.setIconSpaceReserved(false);
-        // Don't directly save to shared prefs, use LauncherPrefs instead.
-        seekBarPref.setPersistent(false);
-        seekBarPref.setOnPreferenceChangeListener((preference, newValue) -> {
-            LauncherPrefs.get(getContext()).put(launcherPref, newValue);
-            preference.setSummary(String.valueOf(scale == 1 ? newValue
-                    : (int) newValue / (float) scale));
-            mFlagTogglerPrefUi.updateMenu();
-            return true;
-        });
-        int value = LauncherPrefs.get(getContext()).get(launcherPref);
-        seekBarPref.setValue(value);
-        // For some reason the initial value is not triggering the summary update, so call manually.
-        seekBarPref.setSummary(String.valueOf(scale == 1 ? value
-                : value / (float) scale));
-        return seekBarPref;
-    }
-
     private String toName(String action) {
         String str = action.replace("com.android.systemui.action.PLUGIN_", "")
                 .replace("com.android.launcher3.action.PLUGIN_", "");
+
         StringBuilder b = new StringBuilder();
         for (String s : str.split("_")) {
             if (b.length() != 0) {
@@ -466,11 +342,11 @@
     private static class PluginPreference extends SwitchPreference {
         private final String mPackageName;
         private final ResolveInfo mSettingsInfo;
-        private final PreferenceDataStore mPluginEnabler;
+        private final PluginEnabler mPluginEnabler;
         private final List<ComponentName> mComponentNames;
 
         PluginPreference(Context prefContext, ResolveInfo pluginInfo,
-                PreferenceDataStore pluginEnabler, List<ComponentName> componentNames) {
+                PluginEnabler pluginEnabler, List<ComponentName> componentNames) {
             super(prefContext);
             PackageManager pm = prefContext.getPackageManager();
             mPackageName = pluginInfo.serviceInfo.applicationInfo.packageName;
@@ -495,14 +371,9 @@
             setWidgetLayoutResource(R.layout.switch_preference_with_settings);
         }
 
-        private boolean isEnabled(ComponentName cn) {
-            return mPluginEnabler.getBoolean(pluginEnabledKey(cn), true);
-
-        }
-
         private boolean isPluginEnabled() {
             for (ComponentName componentName : mComponentNames) {
-                if (!isEnabled(componentName)) {
+                if (!mPluginEnabler.isEnabled(componentName)) {
                     return false;
                 }
             }
@@ -513,8 +384,9 @@
         protected boolean persistBoolean(boolean isEnabled) {
             boolean shouldSendBroadcast = false;
             for (ComponentName componentName : mComponentNames) {
-                if (isEnabled(componentName) != isEnabled) {
-                    mPluginEnabler.putBoolean(pluginEnabledKey(componentName), isEnabled);
+                if (mPluginEnabler.isEnabled(componentName) != isEnabled) {
+                    mPluginEnabler.setDisabled(componentName,
+                            isEnabled ? PluginEnabler.ENABLED : PluginEnabler.DISABLED_MANUALLY);
                     shouldSendBroadcast = true;
                 }
             }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
index 4326c67..fc39ce4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
@@ -34,7 +34,6 @@
 import androidx.preference.PreferenceViewHolder;
 import androidx.preference.SwitchPreference;
 
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
 
@@ -162,22 +161,12 @@
         return mDataStore.getBoolean(flag.key, defaultValue);
     }
 
-    private int getIntFlagStateFromSharedPrefs(IntDebugFlag flag) {
-        LauncherPrefs prefs = LauncherPrefs.get(mContext);
-        return flag.launcherPrefFlag == null ? flag.get() : prefs.get(flag.launcherPrefFlag);
-    }
-
     private boolean anyChanged() {
         for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
             if (getFlagStateFromSharedPrefs(flag) != flag.get()) {
                 return true;
             }
         }
-        for (IntDebugFlag flag : FlagsFactory.getIntDebugFlags()) {
-            if (getIntFlagStateFromSharedPrefs(flag) != flag.get()) {
-                return true;
-            }
-        }
         return false;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index 686ed64..7fd6344 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -23,8 +23,6 @@
 import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
-import static java.util.Collections.unmodifiableList;
-
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.provider.DeviceConfig;
@@ -32,13 +30,9 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
-import com.android.launcher3.ConstantItem;
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.config.FeatureFlags.BooleanFlag;
 import com.android.launcher3.config.FeatureFlags.FlagState;
-import com.android.launcher3.config.FeatureFlags.IntFlag;
 import com.android.launcher3.util.ScreenOnTracker;
 
 import java.io.PrintWriter;
@@ -62,7 +56,6 @@
     public static final String NAMESPACE_LAUNCHER = "launcher";
 
     private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
-    private static final List<IntDebugFlag> sIntDebugFlags = new ArrayList<>();
     private static SharedPreferences sSharedPreferences;
 
     static final BooleanFlag TEAMFOOD_FLAG = getReleaseFlag(
@@ -132,42 +125,6 @@
         }
     }
 
-    /**
-     * Creates a new integer flag. Integer flags are always release flags
-     */
-    public static IntFlag getIntFlag(
-            int bugId, String key, int defaultValueInCode, String description) {
-        return getIntFlag(bugId, key, defaultValueInCode, description, null);
-    }
-
-    /**
-     * Creates a new integer flag.
-     *
-     * @param launcherPrefFlag Set launcherPrefFlag to non-null if you want
-     * to modify the int flag in Launcher Developer Options and IntDebugFlag
-     * will be backed up by LauncherPrefs. Modified int value will be saved
-     * in LauncherPrefs.
-     */
-    public static IntFlag getIntFlag(
-            int bugId, String key, int defaultValueInCode, String description,
-            @Nullable ConstantItem<Integer> launcherPrefFlag) {
-        INSTANCE.mKeySet.add(key);
-        int defaultValue = DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode);
-        if (IS_DEBUG_DEVICE) {
-            int currentValue;
-            if (launcherPrefFlag == null) {
-                currentValue = defaultValue;
-            } else {
-                currentValue = LauncherPrefs.get(currentApplication()).get(launcherPrefFlag);
-            }
-            IntDebugFlag flag = new IntDebugFlag(key, currentValue, defaultValueInCode,
-                    launcherPrefFlag);
-            sIntDebugFlags.add(flag);
-            return flag;
-        } else {
-            return new IntFlag(defaultValue);
-        }
-    }
 
     static List<DebugFlag> getDebugFlags() {
         if (!IS_DEBUG_DEVICE) {
@@ -178,15 +135,6 @@
         }
     }
 
-    static List<IntDebugFlag> getIntDebugFlags() {
-        if (!IS_DEBUG_DEVICE) {
-            return unmodifiableList(Collections.emptyList());
-        }
-        synchronized (sIntDebugFlags) {
-            return unmodifiableList(sIntDebugFlags);
-        }
-    }
-
     /** Returns the SharedPreferences instance backing Debug FeatureFlags. */
     @NonNull
     static SharedPreferences getSharedPreferences() {
@@ -214,12 +162,6 @@
                 }
             }
         }
-        pw.println("  IntFlags:");
-        synchronized (sIntDebugFlags) {
-            for (IntFlag flag : sIntDebugFlags) {
-                pw.println("    " + flag);
-            }
-        }
         pw.println("  DebugFlags:");
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/IntDebugFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/IntDebugFlag.java
deleted file mode 100644
index 1350aa8..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/IntDebugFlag.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.uioverrides.flags;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.ConstantItem;
-import com.android.launcher3.config.FeatureFlags.IntFlag;
-
-public class IntDebugFlag extends IntFlag {
-    public final String key;
-    private final int mDefaultValueInCode;
-    @Nullable
-    public final ConstantItem<Integer> launcherPrefFlag;
-
-    public IntDebugFlag(String key, int currentValue, int defaultValueInCode,
-            @Nullable ConstantItem<Integer> launcherPrefFlag) {
-        super(currentValue);
-        this.key = key;
-        mDefaultValueInCode = defaultValueInCode;
-        this.launcherPrefFlag = launcherPrefFlag;
-    }
-
-    @Override
-    public String toString() {
-        return key + ": mCurrentValue=" + get() + ", defaultValueInCode=" + mDefaultValueInCode;
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java
index faa900b..4e09f1f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginEnablerImpl.java
@@ -18,12 +18,10 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 
-import androidx.preference.PreferenceDataStore;
-
 import com.android.launcher3.LauncherPrefs;
 import com.android.systemui.shared.plugins.PluginEnabler;
 
-public class PluginEnablerImpl extends PreferenceDataStore implements PluginEnabler {
+public class PluginEnablerImpl implements PluginEnabler {
 
     private static final String PREFIX_PLUGIN_ENABLED = "PLUGIN_ENABLED_";
 
@@ -44,12 +42,12 @@
     }
 
     private void setState(ComponentName component, boolean enabled) {
-        putBoolean(pluginEnabledKey(component), enabled);
+        mSharedPrefs.edit().putBoolean(pluginEnabledKey(component), enabled).apply();
     }
 
     @Override
     public boolean isEnabled(ComponentName component) {
-        return getBoolean(pluginEnabledKey(component), true);
+        return mSharedPrefs.getBoolean(pluginEnabledKey(component), true);
     }
 
     @Override
@@ -57,17 +55,7 @@
         return isEnabled(componentName) ? ENABLED : DISABLED_MANUALLY;
     }
 
-    @Override
-    public void putBoolean(String key, boolean value) {
-        mSharedPrefs.edit().putBoolean(key, value).apply();
-    }
-
-    @Override
-    public boolean getBoolean(String key, boolean defValue) {
-        return mSharedPrefs.getBoolean(key, defValue);
-    }
-
-    static String pluginEnabledKey(ComponentName cn) {
+    private static String pluginEnabledKey(ComponentName cn) {
         return PREFIX_PLUGIN_ENABLED + cn.flattenToString();
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index 7f78713..a09d0a1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -98,13 +98,6 @@
         return new PluginPrefs(mContext).getPluginList();
     }
 
-    /**
-     * Returns the string key used to store plugin enabled/disabled setting
-     */
-    public static String pluginEnabledKey(ComponentName cn) {
-        return PluginEnablerImpl.pluginEnabledKey(cn);
-    }
-
     public static boolean hasPlugins(Context context) {
         return PluginPrefs.hasPlugins(context);
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 547de77..3ffad70 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -91,8 +91,8 @@
 
     @Override
     protected float getDepthUnchecked(Context context) {
-        if (Launcher.getLauncher(context).areFreeformTasksVisible()) {
-            // Don't blur the background while freeform tasks are visible
+        if (Launcher.getLauncher(context).areDesktopTasksVisible()) {
+            // Don't blur the background while desktop tasks are visible
             return BaseDepthController.DEPTH_0_PERCENT;
         } else if (enableScalingRevealHomeAnimation()) {
             return BaseDepthController.DEPTH_70_PERCENT;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index 7fb811d..dfad409 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -45,8 +45,8 @@
 
     @Override
     public int getWorkspaceScrimColor(Launcher launcher) {
-        if (launcher.areFreeformTasksVisible()) {
-            // No scrim while freeform tasks are visible
+        if (launcher.areDesktopTasksVisible()) {
+            // No scrim while desktop tasks are visible
             return Color.TRANSPARENT;
         }
         DeviceProfile dp = launcher.getDeviceProfile();
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 62e823a..7ee7751 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -22,8 +22,8 @@
 import static android.widget.Toast.LENGTH_SHORT;
 
 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
-import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.app.animation.Interpolators.DECELERATE;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
@@ -31,7 +31,6 @@
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.Flags.enableGridOnlyOverview;
-import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
 import static com.android.launcher3.PagedView.INVALID_PAGE;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
@@ -100,7 +99,6 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
@@ -892,7 +890,7 @@
     @UiThread
     @Override
     public void onCurrentShiftUpdated() {
-        float threshold = LauncherPrefs.get(mContext).get(ALL_APPS_OVERVIEW_THRESHOLD) / 100f;
+        float threshold = DeviceConfigWrapper.get().getAllAppsOverviewThreshold() / 100f;
         setIsInAllAppsRegion(mCurrentShift.value >= threshold);
         updateSysUiFlags(mCurrentShift.value);
         applyScrollAndTransform();
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index a3f6be0..a3a5f82 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -109,12 +109,12 @@
             // We were on our way to this state when we got canceled, end there instead.
             startState = stateFromGestureEndTarget(endTarget);
             DesktopVisibilityController controller = getDesktopVisibilityController();
-            if (controller != null && controller.areFreeformTasksVisible()
+            if (controller != null && controller.areDesktopTasksVisible()
                     && endTarget == LAST_TASK) {
                 // When we are cancelling the transition and going back to last task, move to
                 // rest state instead when desktop tasks are visible.
                 // If a fullscreen task is visible, launcher goes to normal state when the
-                // activity is stopped. This does not happen when freeform tasks are visible
+                // activity is stopped. This does not happen when desktop tasks are visible
                 // on top of launcher. Force the launcher state to rest state here.
                 startState = activity.getStateManager().getRestState();
                 // Do not animate the transition
diff --git a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
new file mode 100644
index 0000000..8c71d92
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.view.View
+import com.android.launcher3.AbstractFloatingViewHelper
+import com.android.launcher3.BaseDraggingActivity
+import com.android.launcher3.R
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent
+import com.android.launcher3.popup.SystemShortcut
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import com.android.window.flags.Flags
+
+/** A menu item, "Desktop", that allows the user to bring the current app into Desktop Windowing. */
+class DesktopSystemShortcut(
+    activity: BaseDraggingActivity,
+    private val mTaskContainer: TaskIdAttributeContainer,
+    abstractFloatingViewHelper: AbstractFloatingViewHelper
+) :
+    SystemShortcut<BaseDraggingActivity>(
+        R.drawable.ic_caption_desktop_button_foreground,
+        R.string.recent_task_option_desktop,
+        activity,
+        mTaskContainer.itemInfo,
+        mTaskContainer.taskView,
+        abstractFloatingViewHelper
+    ) {
+    override fun onClick(view: View) {
+        dismissTaskMenuView()
+        val recentsView = mTarget!!.getOverviewPanel<RecentsView<*, *>>()
+        recentsView.moveTaskToDesktop(mTaskContainer) {
+            mTarget.statsLogManager
+                .logger()
+                .withItemInfo(mTaskContainer.itemInfo)
+                .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP)
+        }
+    }
+
+    companion object {
+        /** Creates a factory for creating Desktop system shorcuts. */
+        @JvmOverloads
+        fun createFactory(
+            abstractFloatingViewHelper: AbstractFloatingViewHelper = AbstractFloatingViewHelper()
+        ): TaskShortcutFactory {
+            return object : TaskShortcutFactory {
+                override fun getShortcuts(
+                    activity: BaseDraggingActivity,
+                    taskContainer: TaskIdAttributeContainer
+                ): List<DesktopSystemShortcut>? {
+                    return if (!Flags.enableDesktopWindowingMode()) null
+                    else if (!taskContainer.task.isDockable) null
+                    else
+                        listOf(
+                            DesktopSystemShortcut(
+                                activity,
+                                taskContainer,
+                                abstractFloatingViewHelper
+                            )
+                        )
+                }
+
+                override fun showForSplitscreen() = true
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
new file mode 100644
index 0000000..678709c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import com.android.quickstep.util.DeviceConfigHelper
+import com.android.quickstep.util.DeviceConfigHelper.PropReader
+import java.io.PrintWriter
+
+/** Various configurations specific to nav-bar functionalities */
+class DeviceConfigWrapper private constructor(propReader: PropReader) {
+
+    val customLpnhThresholds =
+        propReader.get(
+            "CUSTOM_LPNH_THRESHOLDS",
+            true,
+            "Add dev options and server side control to customize the LPNH trigger slop and milliseconds"
+        )
+
+    val customLphThresholds =
+        propReader.get(
+            "CUSTOM_LPH_THRESHOLDS",
+            false,
+            "Server side control to customize LPH timeout and touch slop"
+        )
+
+    val overrideLpnhLphThresholds =
+        propReader.get(
+            "OVERRIDE_LPNH_LPH_THRESHOLDS",
+            false,
+            "Enable AGSA override for LPNH and LPH timeout and touch slop"
+        )
+
+    val lpnhSlopPercentage =
+        propReader.get("LPNH_SLOP_PERCENTAGE", 100, "Controls touch slop percentage for lpnh")
+
+    val animateLpnh = propReader.get("ANIMATE_LPNH", false, "Animates navbar when long pressing")
+
+    val shrinkNavHandleOnPress =
+        propReader.get(
+            "SHRINK_NAV_HANDLE_ON_PRESS",
+            false,
+            "Shrinks navbar when long pressing if ANIMATE_LPNH is enabled"
+        )
+
+    val lpnhTimeoutMs =
+        propReader.get("LPNH_TIMEOUT_MS", 450, "Controls lpnh timeout in milliseconds")
+
+    val enableLongPressNavHandle =
+        propReader.get(
+            "ENABLE_LONG_PRESS_NAV_HANDLE",
+            true,
+            "Enables long pressing on the bottom bar nav handle to trigger events."
+        )
+
+    val enableSearchHapticHint =
+        propReader.get(
+            "ENABLE_SEARCH_HAPTIC_HINT",
+            true,
+            "Enables haptic hint while long pressing on the bottom bar nav handle."
+        )
+
+    val enableSearchHapticCommit =
+        propReader.get(
+            "ENABLE_SEARCH_HAPTIC_COMMIT",
+            true,
+            "Enables haptic hint at end of long pressing on the bottom bar nav handle."
+        )
+
+    val lpnhHapticHintStartScalePercent =
+        propReader.get("LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0, "Haptic hint start scale.")
+
+    val lpnhHapticHintEndScalePercent =
+        propReader.get("LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100, "Haptic hint end scale.")
+
+    val lpnhHapticHintScaleExponent =
+        propReader.get("LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1, "Haptic hint scale exponent.")
+
+    val lpnhHapticHintIterations =
+        propReader.get("LPNH_HAPTIC_HINT_ITERATIONS", 50, "Haptic hint number of iterations.")
+
+    val enableLpnhDeepPress =
+        propReader.get(
+            "ENABLE_LPNH_DEEP_PRESS",
+            true,
+            "Long press of nav handle is instantly triggered if deep press is detected."
+        )
+
+    val lpnhHapticHintDelay =
+        propReader.get("LPNH_HAPTIC_HINT_DELAY", 0, "Delay before haptic hint starts.")
+
+    val lpnhExtraTouchWidthDp =
+        propReader.get(
+            "LPNH_EXTRA_TOUCH_WIDTH_DP",
+            0,
+            "Controls extra dp on the nav bar sides to trigger LPNH. Can be negative for a smaller touch region."
+        )
+
+    val allAppsOverviewThreshold =
+        propReader.get(
+            "ALL_APPS_OVERVIEW_THRESHOLD",
+            180,
+            "Threshold to open All Apps from Overview"
+        )
+
+    /** Dump config values. */
+    fun dump(prefix: String, writer: PrintWriter) {
+        writer.println("$prefix DeviceConfigWrapper:")
+        writer.println("$prefix\tcustomLpnhThresholds=$customLpnhThresholds")
+        writer.println("$prefix\tcustomLphThresholds=$customLphThresholds")
+        writer.println("$prefix\toverrideLpnhLphThresholds=$overrideLpnhLphThresholds")
+        writer.println("$prefix\tlpnhSlopPercentage=$lpnhSlopPercentage")
+        writer.println("$prefix\tanimateLpnh=$animateLpnh")
+        writer.println("$prefix\tshrinkNavHandleOnPress=$shrinkNavHandleOnPress")
+        writer.println("$prefix\tlpnhTimeoutMs=$lpnhTimeoutMs")
+        writer.println("$prefix\tenableLongPressNavHandle=$enableLongPressNavHandle")
+        writer.println("$prefix\tenableSearchHapticHint=$enableSearchHapticHint")
+        writer.println("$prefix\tenableSearchHapticCommit=$enableSearchHapticCommit")
+        writer.println("$prefix\tlpnhHapticHintStartScalePercent=$lpnhHapticHintStartScalePercent")
+        writer.println("$prefix\tlpnhHapticHintEndScalePercent=$lpnhHapticHintEndScalePercent")
+        writer.println("$prefix\tlpnhHapticHintScaleExponent=$lpnhHapticHintScaleExponent")
+        writer.println("$prefix\tlpnhHapticHintIterations=$lpnhHapticHintIterations")
+        writer.println("$prefix\tenableLpnhDeepPress=$enableLpnhDeepPress")
+        writer.println("$prefix\tlpnhHapticHintDelay=$lpnhHapticHintDelay")
+        writer.println("$prefix\tlpnhExtraTouchWidthDp=$lpnhExtraTouchWidthDp")
+        writer.println("$prefix\tallAppsOverviewThreshold=$allAppsOverviewThreshold")
+    }
+
+    companion object {
+        val configHelper by lazy { DeviceConfigHelper(::DeviceConfigWrapper) }
+
+        @JvmStatic fun get() = configHelper.config
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index c56a621..4b4f914 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -60,7 +60,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
@@ -68,6 +67,7 @@
 import com.android.launcher3.util.SettingsCache;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
 import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.AssistStateManager;
 import com.android.quickstep.util.GestureExclusionManager;
 import com.android.quickstep.util.GestureExclusionManager.ExclusionListener;
 import com.android.quickstep.util.NavBarPosition;
@@ -97,7 +97,7 @@
     private final DisplayController mDisplayController;
 
     private final GestureExclusionManager mExclusionManager;
-
+    private final AssistStateManager mAssistStateManager;
 
     private final RotationTouchHelper mRotationTouchHelper;
     private final TaskStackChangeListener mPipListener;
@@ -148,6 +148,7 @@
         mContext = context;
         mDisplayController = DisplayController.INSTANCE.get(context);
         mExclusionManager = exclusionManager;
+        mAssistStateManager = AssistStateManager.INSTANCE.get(context);
         mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
         mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
         if (isInstanceForTouches) {
@@ -588,9 +589,8 @@
                 : QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON;
         float touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
 
-        if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
-            float customSlopMultiplier =
-                    FeatureFlags.LPNH_SLOP_PERCENTAGE.get() / 100f;
+        if (mAssistStateManager.getLPNHCustomSlopMultiplier().isPresent()) {
+            float customSlopMultiplier = mAssistStateManager.getLPNHCustomSlopMultiplier().get();
             return customSlopMultiplier * slopMultiplier * touchSlop;
         } else {
             return slopMultiplier * touchSlop;
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 0ce4d0a..856e8af 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -70,7 +70,7 @@
         DesktopVisibilityController desktopVisibilityController =
                 LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
         if (desktopVisibilityController != null) {
-            int visibleTasksCount = desktopVisibilityController.getVisibleFreeformTasksCount();
+            int visibleTasksCount = desktopVisibilityController.getVisibleDesktopTasksCount();
             if (visibleTasksCount > 0) {
                 // Allocate +1 to account for a new task added to the desktop mode
                 int numHandles = visibleTasksCount + 1;
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index ab609fd..30bb863 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -836,7 +836,9 @@
      */
     public void setBubbleBarBounds(Rect bubbleBarBounds) {
         try {
-            mBubbles.setBubbleBarBounds(bubbleBarBounds);
+            if (mBubbles != null) {
+                mBubbles.setBubbleBarBounds(bubbleBarBounds);
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "Failed call setBubbleBarBounds");
         }
@@ -1473,6 +1475,17 @@
         }
     }
 
+    /** Call shell to move a task with given `taskId` to desktop  */
+    public void moveToDesktop(int taskId) {
+        if (mDesktopMode != null) {
+            try {
+                mDesktopMode.moveToDesktop(taskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call moveToDesktop", e);
+            }
+        }
+    }
+
     //
     // Unfold transition
     //
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 2fa3001..64c4ff4 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -137,6 +137,7 @@
             TaskShortcutFactory.PIN,
             TaskShortcutFactory.INSTALL,
             TaskShortcutFactory.FREE_FORM,
+            DesktopSystemShortcut.Companion.createFactory(),
             TaskShortcutFactory.WELLBEING,
             TaskShortcutFactory.SAVE_APP_PAIR
     };
@@ -316,7 +317,7 @@
             @Override
             public void onClick(View view) {
                 saveScreenshot(mThumbnailView.getTaskView().getTask());
-                dismissTaskMenuView(mActivity);
+                dismissTaskMenuView();
             }
         }
 
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 147a3e2..5e970f1 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -145,7 +145,7 @@
 
         @Override
         public void onClick(View view) {
-            dismissTaskMenuView(mTarget);
+            dismissTaskMenuView();
             ((RecentsView) mTarget.getOverviewPanel())
                     .getSplitSelectController().getAppPairsController().saveAppPair(mTaskView);
         }
@@ -174,7 +174,7 @@
 
         @Override
         public void onClick(View view) {
-            dismissTaskMenuView(mTarget);
+            dismissTaskMenuView();
             RecentsView rv = mTarget.getOverviewPanel();
             rv.switchToScreenshot(() -> {
                 rv.finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> {
@@ -420,7 +420,7 @@
                 SystemUiProxy.INSTANCE.get(mTarget).startScreenPinning(
                         mTaskView.getTask().key.id);
             }
-            dismissTaskMenuView(mTarget);
+            dismissTaskMenuView();
             mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
                     .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
         }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 66d7144..8f1bf34 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -602,23 +602,21 @@
     }
 
     private PendingIntent createAllAppsPendingIntent() {
-        final Intent homeIntent = new Intent(mOverviewComponentObserver.getHomeIntent())
-                .setAction(INTENT_ACTION_ALL_APPS_TOGGLE);
-
         if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
             return new PendingIntent(new IIntentSender.Stub() {
                 @Override
                 public void send(int code, Intent intent, String resolvedType,
                         IBinder allowlistToken, IIntentReceiver finishedReceiver,
                         String requiredPermission, Bundle options) {
-                    MAIN_EXECUTOR.execute(() -> mTaskbarManager.toggleAllApps(homeIntent));
+                    MAIN_EXECUTOR.execute(() -> mTaskbarManager.toggleAllApps());
                 }
             });
         } else {
             return PendingIntent.getActivity(
                     this,
                     GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
-                    homeIntent,
+                    new Intent(mOverviewComponentObserver.getHomeIntent())
+                            .setAction(INTENT_ACTION_ALL_APPS_TOGGLE),
                     PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         }
     }
@@ -1456,6 +1454,7 @@
         pw.println("AssistStateManager:");
         AssistStateManager.INSTANCE.get(this).dump("  ", pw);
         SystemUiProxy.INSTANCE.get(this).dump(pw);
+        DeviceConfigWrapper.get().dump("   ", pw);
     }
 
     private AbsSwipeUpHandler createLauncherSwipeHandler(
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index e22703b..5ab2fcc 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -27,9 +27,9 @@
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.DeviceConfigWrapper;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.NavHandle;
 import com.android.quickstep.RecentsAnimationDeviceState;
@@ -64,7 +64,7 @@
             NavHandle navHandle) {
         super(delegate, inputMonitor);
         mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
-        mDeepPressEnabled = FeatureFlags.ENABLE_LPNH_DEEP_PRESS.get();
+        mDeepPressEnabled = DeviceConfigWrapper.get().getEnableLpnhDeepPress();
         AssistStateManager assistStateManager = AssistStateManager.INSTANCE.get(context);
         if (assistStateManager.getLPNHDurationMillis().isPresent()) {
             mLongPressTimeout = assistStateManager.getLPNHDurationMillis().get().intValue();
@@ -181,8 +181,9 @@
 
     private boolean isInNavBarHorizontalArea(float x) {
         float areaFromMiddle = mNavHandleWidth / 2.0f;
-        if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
-            areaFromMiddle += Utilities.dpToPx(FeatureFlags.LPNH_EXTRA_TOUCH_WIDTH_DP.get());
+        if (DeviceConfigWrapper.get().getCustomLpnhThresholds()) {
+            areaFromMiddle += Utilities.dpToPx(
+                    DeviceConfigWrapper.get().getLpnhExtraTouchWidthDp());
         }
         int minAccessibleSize = Utilities.dpToPx(24);  // Half of 48dp because this is per side.
         if (areaFromMiddle < minAccessibleSize) {
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index 16f2065..561e951 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -18,7 +18,6 @@
 import static com.android.app.animation.Interpolators.DECELERATE;
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.launcher3.Flags.enableGridOnlyOverview;
-import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
@@ -35,7 +34,6 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -44,6 +42,7 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.AllAppsSwipeController;
+import com.android.quickstep.DeviceConfigWrapper;
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
 import com.android.quickstep.views.RecentsView;
 
@@ -191,7 +190,7 @@
                         recentsOrientedState.getOrientationHandler());
         float dragLengthFactor = (float) dp.heightPx / transitionDragLength;
         // -1s are because 0-1 is reserved for the normal transition.
-        float threshold = LauncherPrefs.get(context).get(ALL_APPS_OVERVIEW_THRESHOLD) / 100f;
+        float threshold = DeviceConfigWrapper.get().getAllAppsOverviewThreshold() / 100f;
         return (threshold - 1) / (dragLengthFactor - 1);
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 59bf105..3ed3e40 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -45,7 +45,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.R;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.apppairs.AppPairIcon;
@@ -158,8 +157,6 @@
                 member.bitmap = iconCache.getDefaultIcon(newAppPair.user);
                 iconCache.getTitleAndIcon(member, member.usingLowResIcon());
             });
-            newAppPair.title = getDefaultTitle(newAppPair.getFirstApp().title,
-                    newAppPair.getSecondApp().title);
             MAIN_EXECUTOR.execute(() -> {
                 LauncherAccessibilityDelegate delegate =
                         Launcher.getLauncher(mContext).getAccessibilityDelegate();
@@ -489,13 +486,6 @@
     }
 
     /**
-     * Returns a formatted default title for the app pair.
-     */
-    public String getDefaultTitle(CharSequence app1, CharSequence app2) {
-        return mContext.getString(R.string.app_pair_default_title, app1, app2);
-    }
-
-    /**
      * Gets the TopTaskTracker, which is a cached record of the top running Task.
      */
     @VisibleForTesting
diff --git a/quickstep/src/com/android/quickstep/util/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
index a1fdbbb..a3904bc 100644
--- a/quickstep/src/com/android/quickstep/util/AssistStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
@@ -52,16 +52,38 @@
         return Optional.empty();
     }
 
-    /** Get the Launcher overridden long press duration to trigger Assistant. */
+    /** Get the Launcher overridden long press nav handle duration to trigger Assistant. */
     public Optional<Long> getLPNHDurationMillis() {
         return Optional.empty();
     }
 
-    /** Get the Launcher overridden long press touch slop multiplier to trigger Assistant. */
-    public Optional<Long> getLPNHCustomSlopMultiplier() {
+    /**
+     * Get the Launcher overridden long press nav handle touch slop multiplier to trigger Assistant.
+     */
+    public Optional<Float> getLPNHCustomSlopMultiplier() {
         return Optional.empty();
     }
 
+    /** Get the Launcher overridden long press home duration to trigger Assistant. */
+    public Optional<Long> getLPHDurationMillis() {
+        return Optional.empty();
+    }
+
+    /** Get the Launcher overridden long press home touch slop multiplier to trigger Assistant. */
+    public Optional<Float> getLPHCustomSlopMultiplier() {
+        return Optional.empty();
+    }
+
+    /** Get the long press duration data source. */
+    public int getDurationDataSource() {
+        return 0;
+    }
+
+    /** Get the long press touch slop multiplier data source. */
+    public int getSlopDataSource() {
+        return 0;
+    }
+
     /** Return {@code true} if the Settings toggle is enabled. */
     public boolean isSettingsAllEntrypointsEnabled() {
         return false;
diff --git a/quickstep/src/com/android/quickstep/util/DeviceConfigHelper.kt b/quickstep/src/com/android/quickstep/util/DeviceConfigHelper.kt
new file mode 100644
index 0000000..f601fee
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/DeviceConfigHelper.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 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.ActivityThread
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.SharedPreferences.*
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.OnPropertiesChangedListener
+import android.provider.DeviceConfig.Properties
+import androidx.annotation.WorkerThread
+import com.android.launcher3.BuildConfig
+import com.android.launcher3.uioverrides.flags.FlagsFactory
+import com.android.launcher3.util.Executors
+
+/** Utility class to manage a set of device configurations */
+class DeviceConfigHelper<ConfigType>(private val factory: (PropReader) -> ConfigType) {
+
+    var config: ConfigType
+        private set
+    private val allKeys: Set<String>
+    private val propertiesListener = OnPropertiesChangedListener { onDevicePropsChanges(it) }
+    private val sharedPrefChangeListener = OnSharedPreferenceChangeListener { _, _ ->
+        recreateConfig()
+    }
+
+    private val changeListeners = mutableListOf<Runnable>()
+
+    init {
+        // Initialize the default config once.
+        allKeys = HashSet()
+        config =
+            factory(
+                PropReader(
+                    object : PropProvider {
+                        override fun <T : Any> get(key: String, fallback: T): T {
+                            if (fallback is Int)
+                                return DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, fallback) as T
+                            else if (fallback is Boolean)
+                                return DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, fallback)
+                                    as T
+                            else return fallback
+                        }
+                    }
+                )
+            )
+
+        DeviceConfig.addOnPropertiesChangedListener(
+            NAMESPACE_LAUNCHER,
+            Executors.UI_HELPER_EXECUTOR,
+            propertiesListener
+        )
+        if (BuildConfig.IS_DEBUG_DEVICE) {
+            prefs.registerOnSharedPreferenceChangeListener(sharedPrefChangeListener)
+        }
+    }
+
+    @WorkerThread
+    private fun onDevicePropsChanges(properties: Properties) {
+        if (NAMESPACE_LAUNCHER != properties.namespace) return
+        if (!allKeys.any(properties.keyset::contains)) return
+        recreateConfig()
+    }
+
+    private fun recreateConfig() {
+        val myProps =
+            DeviceConfig.getProperties(
+                FlagsFactory.NAMESPACE_LAUNCHER,
+                *allKeys.toTypedArray<String>()
+            )
+        config =
+            factory(
+                PropReader(
+                    object : PropProvider {
+                        override fun <T : Any> get(key: String, fallback: T): T {
+                            if (fallback is Int) return myProps.getInt(key, fallback) as T
+                            else if (fallback is Boolean)
+                                return myProps.getBoolean(key, fallback) as T
+                            else return fallback
+                        }
+                    }
+                )
+            )
+        Executors.MAIN_EXECUTOR.execute { changeListeners.forEach(Runnable::run) }
+    }
+
+    /** Adds a listener for property changes */
+    fun addChangeListener(r: Runnable) = changeListeners.add(r)
+
+    /** Removes a previously added listener */
+    fun removeChangeListener(r: Runnable) = changeListeners.remove(r)
+
+    fun close() {
+        DeviceConfig.removeOnPropertiesChangedListener(propertiesListener)
+        if (BuildConfig.IS_DEBUG_DEVICE) {
+            prefs.unregisterOnSharedPreferenceChangeListener(sharedPrefChangeListener)
+        }
+    }
+
+    internal interface PropProvider {
+        fun <T : Any> get(key: String, fallback: T): T
+    }
+
+    /** The reader is sent to the config for initialization */
+    class PropReader internal constructor(private val f: PropProvider) {
+
+        @JvmOverloads
+        fun <T : Any> get(key: String, fallback: T, desc: String? = null): T {
+            val v = f.get(key, fallback)
+            if (BuildConfig.IS_DEBUG_DEVICE && desc != null) {
+                if (v is Int) {
+                    allProps[key] = DebugInfo(key, desc, true, fallback)
+                    return prefs.getInt(key, v) as T
+                } else if (v is Boolean) {
+                    allProps[key] = DebugInfo(key, desc, false, fallback)
+                    return prefs.getBoolean(key, v) as T
+                }
+            }
+            return v
+        }
+    }
+
+    class DebugInfo<T>(
+        val key: String,
+        val desc: String,
+        val isInt: Boolean,
+        val valueInCode: T,
+    )
+
+    companion object {
+        const val NAMESPACE_LAUNCHER = "launcher"
+
+        val allProps = mutableMapOf<String, DebugInfo<*>>()
+
+        private const val FLAGS_PREF_NAME = "featureFlags"
+
+        val prefs: SharedPreferences by lazy {
+            ActivityThread.currentApplication()
+                .createDeviceProtectedStorageContext()
+                .getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE)
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index 32ef904..acda2e1 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -17,15 +17,28 @@
 package com.android.quickstep.views;
 
 import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
 
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.widget.Button;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
+import com.android.launcher3.R;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
+import com.android.quickstep.util.BorderAnimator;
+
+import kotlin.Unit;
 
 public class ClearAllButton extends Button {
 
@@ -71,11 +84,71 @@
     private float mScrollOffsetPrimary;
 
     private int mSidePadding;
+    private int mOutlinePadding;
+    private boolean mBorderEnabled;
+    @Nullable
+    private final BorderAnimator mFocusBorderAnimator;
 
     public ClearAllButton(Context context, AttributeSet attrs) {
         super(context, attrs);
         mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
         mActivity = StatefulActivity.fromContext(context);
+
+        if (Flags.enableFocusOutline()) {
+            TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
+                    R.styleable.ClearAllButton);
+            Resources resources = getResources();
+            mOutlinePadding = resources.getDimensionPixelSize(
+                    R.dimen.recents_clear_all_outline_padding);
+            mFocusBorderAnimator =
+                    BorderAnimator.createSimpleBorderAnimator(
+                            /* borderRadiusPx= */ resources.getDimensionPixelSize(
+                                    R.dimen.recents_clear_all_outline_radius),
+                            /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
+                                    R.dimen.keyboard_quick_switch_border_width),
+                            /* boundsBuilder= */ this::updateBorderBounds,
+                            /* targetView= */ this,
+                            /* borderColor= */ styledAttrs.getColor(
+                                    R.styleable.ClearAllButton_focusBorderColor,
+                                    DEFAULT_BORDER_COLOR));
+            styledAttrs.recycle();
+        } else {
+            mFocusBorderAnimator = null;
+        }
+    }
+
+    private Unit updateBorderBounds(@NonNull Rect bounds) {
+        bounds.set(0, 0, getWidth(), getHeight());
+        // Make the value negative to form a padding between button and outline
+        bounds.inset(-mOutlinePadding, -mOutlinePadding);
+        return Unit.INSTANCE;
+    }
+
+    @Override
+    public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+        if (mFocusBorderAnimator != null && mBorderEnabled) {
+            mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
+        }
+    }
+
+    /**
+     * Enable or disable showing border on focus change
+     */
+    public void setBorderEnabled(boolean enabled) {
+        mBorderEnabled = enabled;
+        if (mFocusBorderAnimator != null) {
+            mFocusBorderAnimator.setBorderVisibility(/* visible= */
+                    enabled && isFocused(), /* animated= */true);
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mFocusBorderAnimator != null) {
+            mFocusBorderAnimator.drawBorder(canvas);
+        }
+        super.draw(canvas);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 0a3d2a0..352ebfe 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -271,7 +271,7 @@
         if (desktopVisibilityController != null) {
             endTarget = mCurrentGestureEndTarget;
             if (endTarget == GestureState.GestureEndTarget.LAST_TASK
-                    && desktopVisibilityController.areFreeformTasksVisible()) {
+                    && desktopVisibilityController.areDesktopTasksVisible()) {
                 // Recents gesture was cancelled and we are returning to the previous task.
                 // After super class has handled clean up, show desktop apps on top again
                 showDesktopApps = true;
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index eca70b7..2e719cd 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -163,6 +163,7 @@
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.TranslateEdgeEffect;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.util.ViewPool;
@@ -1454,6 +1455,7 @@
             TaskView taskView = requireTaskViewAt(i);
             taskView.setBorderEnabled(enabled);
         }
+        mClearAllButton.setBorderEnabled(enabled);
     }
 
     /**
@@ -1504,6 +1506,15 @@
     }
 
     @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        boolean intercept = super.onInterceptTouchEvent(ev);
+        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            Log.d("b/318590728", "onInterceptTouchEvent: " + ev);
+        }
+        return intercept;
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent ev) {
         super.onTouchEvent(ev);
 
@@ -2569,6 +2580,7 @@
      */
     public void onGestureAnimationStart(
             Task[] runningTasks, RotationTouchHelper rotationTouchHelper) {
+        Log.d(TAG, "onGestureAnimationStart");
         mActiveGestureRunningTasks = runningTasks;
         // This needs to be called before the other states are set since it can create the task view
         if (mOrientationState.setGestureActive(true)) {
@@ -4293,7 +4305,8 @@
      * Updates {@link RecentsOrientedState}'s cached RecentsView rotation.
      */
     public void updateRecentsRotation() {
-        final int rotation = mActivity.getDisplay().getRotation();
+        final int rotation = TraceHelper.allowIpcs(
+                "RecentsView.updateRecentsRotation", () -> mActivity.getDisplay().getRotation());
         mOrientationState.setRecentsRotation(rotation);
     }
 
@@ -5413,6 +5426,8 @@
      * Called when a running recents animation has finished or canceled.
      */
     public void onRecentsAnimationComplete() {
+        Log.d(TAG, "onRecentsAnimationComplete - mRecentsAnimationController: "
+                + mRecentsAnimationController);
         // At this point, the recents animation is not running and if the animation was canceled
         // by a display rotation then reset this state to show the screenshot
         setRunningTaskViewShowScreenshot(true);
@@ -6200,6 +6215,27 @@
         UI_HELPER_EXECUTOR.post(() -> mActivity.setLocusContext(id, Bundle.EMPTY));
     }
 
+    /**
+     * Moves the provided task into desktop mode, and invoke {@code successCallback} if succeeded.
+     */
+    public void moveTaskToDesktop(TaskIdAttributeContainer taskContainer,
+            Runnable successCallback) {
+        if (!enableDesktopWindowingMode()) {
+            return;
+        }
+        switchToScreenshot(() -> finishRecentsAnimation(/* toRecents= */true, /* shouldPip= */false,
+                () -> moveTaskToDesktopInternal(taskContainer, successCallback)));
+    }
+
+    private void moveTaskToDesktopInternal(TaskIdAttributeContainer taskContainer,
+            Runnable successCallback) {
+        if (mDesktopRecentsTransitionController == null) {
+            return;
+        }
+        mDesktopRecentsTransitionController.moveToDesktop(taskContainer.getTask().key.id);
+        successCallback.run();
+    }
+
     public interface TaskLaunchListener {
         void onTaskLaunched();
     }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index cec0982..5338d81 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -423,7 +423,8 @@
         mCurrentFullscreenParams = new FullscreenDrawParams(context);
         mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
 
-        boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get();
+        boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
+                || Flags.enableFocusOutline();
         boolean cursorHoverStatesEnabled = enableCursorHoverStates();
 
         setWillNotDraw(!keyboardFocusHighlightEnabled && !cursorHoverStatesEnabled);
@@ -486,7 +487,11 @@
         return getItemInfo(mTask);
     }
 
-    protected WorkspaceItemInfo getItemInfo(@Nullable Task task) {
+    /**
+     * Builds proto for logging
+     */
+    @VisibleForTesting
+    public WorkspaceItemInfo getItemInfo(@Nullable Task task) {
         WorkspaceItemInfo stubInfo = new WorkspaceItemInfo();
         stubInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
         stubInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
@@ -812,12 +817,18 @@
 
     private void onClick(View view) {
         if (getTask() == null) {
+            Log.d("b/310064698", "onClick - task is null");
             return;
         }
         if (confirmSecondSplitSelectApp()) {
+            Log.d("b/310064698", mTask + " - onClick - split select is active");
             return;
         }
-        launchTasks();
+        RunnableList callbackList = launchTasks();
+        Log.d("b/310064698", mTask + " - onClick - callbackList: " + callbackList);
+        if (callbackList != null) {
+            callbackList.add(() -> Log.d("b/310064698", mTask + " - onClick - launchCompleted"));
+        }
         mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
                 .log(LAUNCHER_TASK_LAUNCH_TAP);
     }
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
similarity index 100%
rename from quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
diff --git a/quickstep/tests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
diff --git a/quickstep/tests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
new file mode 100644
index 0000000..b90839d
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.content.ComponentName
+import android.content.Intent
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.AbstractFloatingViewHelper
+import com.android.launcher3.Launcher
+import com.android.launcher3.logging.StatsLogManager
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.quickstep.views.LauncherRecentsView
+import com.android.quickstep.views.TaskView
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.Task.TaskKey
+import com.android.window.flags.Flags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/** Test for DesktopSystemShortcut */
+class DesktopSystemShortcutTest {
+
+    @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
+
+    private val launcher: Launcher = mock()
+    private val statsLogManager: StatsLogManager = mock()
+    private val statsLogger: StatsLogManager.StatsLogger = mock()
+    private val recentsView: LauncherRecentsView = mock()
+    private val taskView: TaskView = mock()
+    private val workspaceItemInfo: WorkspaceItemInfo = mock()
+    private val abstractFloatingViewHelper: AbstractFloatingViewHelper = mock()
+    private val factory: TaskShortcutFactory =
+        DesktopSystemShortcut.createFactory(abstractFloatingViewHelper)
+
+    @Test
+    fun createDesktopTaskShortcutFactory_featureOff() {
+        setFlagsRule.disableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+
+        val task =
+            Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+                isDockable = true
+            }
+        val taskContainer =
+            taskView.TaskIdAttributeContainer(
+                task,
+                null,
+                null,
+                SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+            )
+
+        val shortcuts = factory.getShortcuts(launcher, taskContainer)
+        assertThat(shortcuts).isNull()
+    }
+
+    @Test
+    fun createDesktopTaskShortcutFactory_undockable() {
+        setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+
+        val task =
+            Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+                isDockable = false
+            }
+        val taskContainer =
+            taskView.TaskIdAttributeContainer(
+                task,
+                null,
+                null,
+                SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+            )
+
+        val shortcuts = factory.getShortcuts(launcher, taskContainer)
+        assertThat(shortcuts).isNull()
+    }
+
+    @Test
+    fun desktopSystemShortcutClicked() {
+        setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+
+        val task =
+            Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+                isDockable = true
+            }
+        val taskContainer =
+            taskView.TaskIdAttributeContainer(
+                task,
+                null,
+                null,
+                SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+            )
+
+        whenever(launcher.getOverviewPanel<LauncherRecentsView>()).thenReturn(recentsView)
+        whenever(launcher.statsLogManager).thenReturn(statsLogManager)
+        whenever(statsLogManager.logger()).thenReturn(statsLogger)
+        whenever(statsLogger.withItemInfo(any())).thenReturn(statsLogger)
+        whenever(taskView.getItemInfo(task)).thenReturn(workspaceItemInfo)
+        whenever(recentsView.moveTaskToDesktop(any(), any())).thenAnswer {
+            val successCallback = it.getArgument<Runnable>(1)
+            successCallback.run()
+        }
+
+        val shortcuts = factory.getShortcuts(launcher, taskContainer)
+        assertThat(shortcuts).hasSize(1)
+        assertThat(shortcuts!!.first()).isInstanceOf(DesktopSystemShortcut::class.java)
+
+        val desktopShortcut = shortcuts.first() as DesktopSystemShortcut
+
+        desktopShortcut.onClick(taskView)
+
+        val allTypesExceptRebindSafe =
+            AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv()
+        verify(abstractFloatingViewHelper).closeOpenViews(launcher, true, allTypesExceptRebindSafe)
+        verify(recentsView).moveTaskToDesktop(eq(taskContainer), any())
+        verify(statsLogger).withItemInfo(workspaceItemInfo)
+        verify(statsLogger).log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP)
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
new file mode 100644
index 0000000..0c143b4
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import com.android.launcher3.tapl.LauncherInstrumentation;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Objects;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplPrivateSpaceTest extends AbstractQuickStepTest {
+
+    private int mProfileUserId;
+    private boolean mPrivateProfileSetupSuccessful;
+    private static final String TAG = "TaplPrivateSpaceTest";
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        initialize(this);
+
+        createAndStartPrivateProfileUser();
+        assumeTrue("Private Profile Setup not successful, aborting",
+                mPrivateProfileSetupSuccessful);
+
+        mDevice.pressHome();
+        waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
+        waitForStateTransitionToEnd("Launcher internal state didn't switch to Normal",
+                () -> NORMAL);
+        waitForResumed("Launcher internal state is still Background");
+        mLauncher.getWorkspace().switchToAllApps();
+        waitForStateTransitionToEnd("Launcher internal state didn't switch to All Apps",
+                () -> ALL_APPS);
+
+        // Wait for Private Space being available in Launcher.
+        waitForPrivateSpaceSetup();
+        // Wait for Launcher UI to be updated with Private Space Items.
+        waitForLauncherUIUpdate();
+    }
+
+    private void createAndStartPrivateProfileUser() {
+        String createUserOutput = executeShellCommand("pm create-user --profileOf 0 --user-type "
+                + "android.os.usertype.profile.PRIVATE LauncherPrivateProfile");
+        updatePrivateProfileSetupSuccessful("pm create-user", createUserOutput);
+        String[] tokens = createUserOutput.split("\\s+");
+        mProfileUserId = Integer.parseInt(tokens[tokens.length - 1]);
+        StringBuilder logStr = new StringBuilder().append("profileId: ").append(mProfileUserId);
+        for (String str : tokens) {
+            logStr.append(str).append("\n");
+        }
+        String startUserOutput = executeShellCommand("am start-user " + mProfileUserId);
+        updatePrivateProfileSetupSuccessful("am start-user", startUserOutput);
+    }
+
+    @After
+    public void removePrivateProfile() {
+        String output = executeShellCommand("pm remove-user " + mProfileUserId);
+        updateProfileRemovalSuccessful("pm remove-user", output);
+        waitForPrivateSpaceRemoval();
+    }
+
+    @Test
+    public void testPrivateSpaceContainerIsPresent() {
+        assumeTrue(mPrivateProfileSetupSuccessful);
+        // Scroll to the bottom of All Apps
+        executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
+        waitForResumed("Launcher internal state is still Background");
+
+        // Verify Unlocked View elements are present.
+        assertNotNull("Private Space Unlocked View not found, or is not correct",
+                mLauncher.getAllApps().getPrivateSpaceUnlockedView());
+    }
+
+    private void waitForPrivateSpaceSetup() {
+        waitForLauncherCondition("Private Profile not setup",
+                launcher -> launcher.getAppsView().hasPrivateProfile(),
+                LauncherInstrumentation.WAIT_TIME_MS);
+    }
+
+    private void waitForPrivateSpaceRemoval() {
+        waitForLauncherCondition("Private Profile not setup",
+                launcher -> !launcher.getAppsView().hasPrivateProfile(),
+                LauncherInstrumentation.WAIT_TIME_MS);
+    }
+
+    private void waitForLauncherUIUpdate() {
+        // Wait for model thread completion as it may be processing
+        // the install event from the SystemService
+        mLauncher.waitForModelQueueCleared();
+        // Wait for Launcher UI thread completion, as it may be processing updating the UI in
+        // response to the model update. Not that `waitForLauncherInitialized` is just a proxy
+        // method, we can use any method which touches Launcher UI thread,
+        mLauncher.waitForLauncherInitialized();
+    }
+
+    private void updatePrivateProfileSetupSuccessful(String cli, String output) {
+        Log.d(TAG, "updatePrivateProfileSetupSuccessful, cli=" + cli + " " + "output="
+                + output);
+        mPrivateProfileSetupSuccessful = output.startsWith("Success");
+    }
+
+    private void updateProfileRemovalSuccessful(String cli, String output) {
+        Log.d(TAG, "updateProfileRemovalSuccessful, cli=" + cli + " " + "output=" + output);
+        assertTrue(output, output.startsWith("Success"));
+    }
+
+    private String executeShellCommand(String command) {
+        try {
+            return mDevice.executeShellCommand(command);
+        } catch (IOException e) {
+            Log.e(TAG, "error running shell command", e);
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java
index b31f470..e4caa26 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplStartLauncherViaGestureTests.java
@@ -16,13 +16,9 @@
 
 package com.android.quickstep;
 
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
-
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
 import org.junit.Before;
@@ -47,8 +43,6 @@
 
     @Test
     @NavigationModeSwitch
-    // Stress tests are long. We permanently demote them from presubmit to match the presubmit SLO.
-    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
     public void testStressPressHome() {
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
@@ -61,8 +55,6 @@
 
     @Test
     @NavigationModeSwitch
-    // Stress tests are long. We permanently demote them from presubmit to match the presubmit SLO.
-    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
     public void testStressSwipeToOverview() {
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
diff --git a/res/layout/develop_options_edit_text.xml b/res/layout/develop_options_edit_text.xml
new file mode 100644
index 0000000..5e44228
--- /dev/null
+++ b/res/layout/develop_options_edit_text.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 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.ExtendedEditText
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:minWidth="100dp"
+    android:inputType="numberSigned"
+    android:id="@+id/pref_edit_text"
+    android:selectAllOnFocus="true"
+    android:imeOptions="actionDone"
+    android:maxLines="1" />
\ No newline at end of file
diff --git a/res/layout/widget_recommendations.xml b/res/layout/widget_recommendations.xml
index 531db2e..5879b0f 100644
--- a/res/layout/widget_recommendations.xml
+++ b/res/layout/widget_recommendations.xml
@@ -33,6 +33,9 @@
         android:textColor="?attr/widgetPickerTitleColor"
         android:textFontWeight="500"
         android:textSize="16sp"
+        android:maxLines="1"
+        android:paddingHorizontal="8dp"
+        android:ellipsize="end"
         android:visibility="gone" />
     <!-- Shown when there are more than one pages -->
     <com.android.launcher3.pageindicators.PageIndicatorDots
diff --git a/res/values/config.xml b/res/values/config.xml
index 599584b..f820e76 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -78,6 +78,7 @@
     <string name="launcher_restore_event_logger_class" translatable="false"></string>
     <!--  Used for determining category of a widget presented in widget recommendations. -->
     <string name="widget_recommendation_category_provider_class" translatable="false"></string>
+    <string name="api_wrapper_class" translatable="false"></string>
 
     <!-- Default packages -->
     <string name="wallpaper_picker_package" translatable="false"></string>
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index e7b88dc..4ccf3db 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -278,18 +278,7 @@
 
     public static void closeOpenViews(ActivityContext activity, boolean animate,
             @FloatingViewType int type) {
-        BaseDragLayer dragLayer = activity.getDragLayer();
-        // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
-        // and will be one of the last views.
-        for (int i = dragLayer.getChildCount() - 1; i >= 0; i--) {
-            View child = dragLayer.getChildAt(i);
-            if (child instanceof AbstractFloatingView) {
-                AbstractFloatingView abs = (AbstractFloatingView) child;
-                if (abs.isOfType(type)) {
-                    abs.close(animate);
-                }
-            }
-        }
+        new AbstractFloatingViewHelper().closeOpenViews(activity, animate, type);
     }
 
     public static void closeAllOpenViews(ActivityContext activity, boolean animate) {
diff --git a/src/com/android/launcher3/AbstractFloatingViewHelper.kt b/src/com/android/launcher3/AbstractFloatingViewHelper.kt
new file mode 100644
index 0000000..0bfbc6e
--- /dev/null
+++ b/src/com/android/launcher3/AbstractFloatingViewHelper.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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
+
+import com.android.launcher3.AbstractFloatingView.FloatingViewType
+import com.android.launcher3.views.ActivityContext
+
+/**
+ * Helper class for manaing AbstractFloatingViews which shows a floating UI on top of the launcher
+ * UI.
+ */
+class AbstractFloatingViewHelper {
+    fun closeOpenViews(activity: ActivityContext, animate: Boolean, @FloatingViewType type: Int) {
+        val dragLayer = activity.getDragLayer()
+        // Iterate in reverse order. AbstractFloatingView is added later to the dragLayer,
+        // and will be one of the last views.
+        for (i in dragLayer.getChildCount() - 1 downTo 0) {
+            val child = dragLayer.getChildAt(i)
+            if (child is AbstractFloatingView && child.isOfType(type)) {
+                child.close(animate)
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index c7cdfa8..cf86528 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -52,7 +52,7 @@
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.qsb.QsbContainerView;
 import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.Partner;
 import com.android.launcher3.util.Thunk;
@@ -203,7 +203,7 @@
         mIdp = LauncherAppState.getIDP(context);
         mRowCount = mIdp.numRows;
         mColumnCount = mIdp.numColumns;
-        mActivityOverride = ApiWrapper.getActivityOverrides(context);
+        mActivityOverride = ApiWrapper.INSTANCE.get(context).getActivityOverrides();
     }
 
     /**
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 3ddc7aa..6cb33a8 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -63,13 +63,13 @@
 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType;
 import com.android.launcher3.responsive.ResponsiveSpec.DimensionType;
 import com.android.launcher3.responsive.ResponsiveSpecsProvider;
-import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.CellContentDimensions;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.IconSizeSteps;
 import com.android.launcher3.util.ResourceHelper;
 import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.util.window.WindowManagerProxy;
 
 import java.io.PrintWriter;
 import java.util.Locale;
@@ -350,7 +350,8 @@
         isTablet = info.isTablet(windowBounds);
         isPhone = !isTablet;
         isTwoPanels = isTablet && isMultiDisplay;
-        isTaskbarPresent = isTablet && ApiWrapper.TASKBAR_DRAWN_IN_PROCESS;
+        isTaskbarPresent = isTablet
+                && WindowManagerProxy.INSTANCE.get(context).isTaskbarDrawnInProcess();
 
         // Some more constants.
         context = getContext(context, info, isVerticalBarLayout() || (isTablet && isLandscape)
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 3273f27..d2d5ba9 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1551,7 +1551,13 @@
             LauncherAppWidgetInfo launcherInfo,
             CellPos presenterPos) {
         CellLayout cellLayout = getCellLayout(launcherInfo.container, presenterPos.screenId);
-        if (mStateManager.getState() == NORMAL) {
+        // We should wait until launcher is not animating to show resize frame so that
+        // {@link View#hasIdentityMatrix()} returns true (no scale effect) from CellLayout and
+        // Workspace (they are widget's parent view). Otherwise widget's
+        // {@link View#getLocationInWindow(int[])} will set skewed location, causing resize
+        // frame not showing at skewed location in
+        // {@link AppWidgetResizeFrame#snapToWidget(boolean)}.
+        if (mStateManager.getState() == NORMAL && !mStateManager.isInTransition()) {
             AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout);
         } else {
             mStateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
@@ -1647,7 +1653,7 @@
         } else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
             showAllAppsFromIntent(alreadyOnHome);
         } else if (INTENT_ACTION_ALL_APPS_TOGGLE.equals(intent.getAction())) {
-            toggleAllAppsFromIntent(alreadyOnHome);
+            toggleAllAppsSearch(alreadyOnHome);
         } else if (Intent.ACTION_SHOW_WORK_APPS.equals(intent.getAction())) {
             showAllAppsWithSelectedTabFromIntent(alreadyOnHome,
                     ActivityAllAppsContainerView.AdapterHolder.WORK);
@@ -1661,7 +1667,12 @@
         // Overridden
     }
 
-    protected void toggleAllAppsFromIntent(boolean alreadyOnHome) {
+    /** Toggles Launcher All Apps with keyboard ready for search. */
+    public void toggleAllAppsSearch() {
+        toggleAllAppsSearch(/* alreadyOnHome= */ true);
+    }
+
+    protected void toggleAllAppsSearch(boolean alreadyOnHome) {
         if (getStateManager().isInStableState(ALL_APPS)) {
             getStateManager().goToState(NORMAL, alreadyOnHome);
         } else {
@@ -2882,8 +2893,8 @@
      * Returns {@code true} if there are visible tasks with windowing mode set to
      * {@link android.app.WindowConfiguration#WINDOWING_MODE_FREEFORM}
      */
-    public boolean areFreeformTasksVisible() {
-        return false; // Base launcher does not track freeform tasks
+    public boolean areDesktopTasksVisible() {
+        return false; // Base launcher does not track desktop tasks
     }
 
     // Getters and Setters
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 60a6be6..50a597d 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -110,7 +110,7 @@
         mOnTerminateCallback.add(() ->
                 mContext.getSystemService(LauncherApps.class).unregisterCallback(callbacks));
 
-        if (Utilities.enableSupportForArchiving()) {
+        if (Flags.enableSupportForArchiving()) {
             ArchiveCompatibilityParams params = new ArchiveCompatibilityParams();
             params.setEnableUnarchivalConfirmation(false);
             launcherApps.setArchiveCompatibility(params);
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index cb19b14..875c407 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -297,64 +297,7 @@
         @JvmField
         val ICON_STATE =
             nonRestorableItem("pref_icon_shape_path", "", EncryptionType.MOVE_TO_DEVICE_PROTECTED)
-        @JvmField
-        val ALL_APPS_OVERVIEW_THRESHOLD =
-            nonRestorableItem(
-                "pref_all_apps_overview_threshold",
-                180,
-                EncryptionType.MOVE_TO_DEVICE_PROTECTED
-            )
-        @JvmField
-        val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
-            nonRestorableItem("LPNH_SLOP_PERCENTAGE", 100, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
-        @JvmField
-        val LONG_PRESS_NAV_HANDLE_EXTRA_TOUCH_WIDTH_DP =
-            nonRestorableItem(
-                "LPNH_EXTRA_TOUCH_WIDTH_DP",
-                0,
-                EncryptionType.MOVE_TO_DEVICE_PROTECTED
-            )
-        @JvmField
-        val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
-            nonRestorableItem(
-                "LPNH_TIMEOUT_MS",
-                450,
-                EncryptionType.MOVE_TO_DEVICE_PROTECTED
-            )
-        @JvmField
-        val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT =
-            nonRestorableItem(
-                "LPNH_HAPTIC_HINT_START_SCALE_PERCENT",
-                0,
-                EncryptionType.MOVE_TO_DEVICE_PROTECTED
-            )
-        @JvmField
-        val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT =
-            nonRestorableItem(
-                "LPNH_HAPTIC_HINT_END_SCALE_PERCENT",
-                100,
-                EncryptionType.MOVE_TO_DEVICE_PROTECTED
-            )
-        @JvmField
-        val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT =
-            nonRestorableItem(
-                "LPNH_HAPTIC_HINT_SCALE_EXPONENT",
-                1,
-                EncryptionType.MOVE_TO_DEVICE_PROTECTED
-            )
-        @JvmField
-        val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS =
-            nonRestorableItem(
-                "LPNH_HAPTIC_HINT_ITERATIONS",
-                50,
-                EncryptionType.MOVE_TO_DEVICE_PROTECTED
-            )
-        @JvmField
-        val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY =
-            nonRestorableItem("LPNH_HAPTIC_HINT_DELAY", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
-        @JvmField
-        val PRIVATE_SPACE_APPS =
-            nonRestorableItem("pref_private_space_apps", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
+
         @JvmField
         val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
         @JvmField
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index d44438f..2b886e4 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -830,10 +830,4 @@
                 // No-Op
         }
     }
-
-    /** Encapsulates two flag checks into a single one. */
-    public static boolean enableSupportForArchiving() {
-        return Flags.enableSupportForArchiving()
-                || getSystemProperty("pm.archiving.enabled", "false").equals("true");
-    }
 }
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index be120cc..1def8a3 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -72,7 +72,7 @@
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.PrivateSpaceInstallAppButtonInfo;
 import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.views.ActivityContext;
@@ -122,7 +122,9 @@
         super(userManager, statsLogManager, userCache);
         mAllApps = allApps;
         mPrivateProfileMatcher = (user) -> userCache.getUserInfo(user).isPrivate();
-        UI_HELPER_EXECUTOR.post(this::initializeInBackgroundThread);
+
+        Context appContext = allApps.getContext().getApplicationContext();
+        UI_HELPER_EXECUTOR.post(() -> initializeInBackgroundThread(appContext));
         mPsHeaderHeight = mAllApps.getContext().getResources().getDimensionPixelSize(
                 R.dimen.ps_header_height);
     }
@@ -187,7 +189,7 @@
     /** Whether private profile should be hidden on Launcher. */
     public boolean isPrivateSpaceHidden() {
         return getCurrentState() == STATE_DISABLED && SettingsCache.INSTANCE
-                    .get(mAllApps.mActivityContext).getValue(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, 0);
+                    .get(mAllApps.getContext()).getValue(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, 0);
     }
 
     /**
@@ -215,8 +217,8 @@
     /** Opens the Private Space Settings Page. */
     public void openPrivateSpaceSettings() {
         if (mPrivateSpaceSettingsAvailable) {
-            mAllApps.getContext()
-                    .startActivity(ApiWrapper.getPrivateSpaceSettingsIntent(mAllApps.getContext()));
+            mAllApps.getContext().startActivity(
+                    ApiWrapper.INSTANCE.get(mAllApps.getContext()).getPrivateSpaceSettingsIntent());
         }
     }
 
@@ -239,33 +241,17 @@
      * This case should still be ok, as locking the Private Space container and unlocking it,
      * reloads the values, fixing the incorrect UI.
      */
-    private void initializeInBackgroundThread() {
+    private void initializeInBackgroundThread(Context appContext) {
         Preconditions.assertNonUiThread();
-        setPreInstalledSystemPackages();
-        setAppInstallerIntent();
-        initializePrivateSpaceSettingsState();
-    }
-
-    private void initializePrivateSpaceSettingsState() {
-        Preconditions.assertNonUiThread();
-        Intent psSettingsIntent = ApiWrapper.getPrivateSpaceSettingsIntent(mAllApps.getContext());
-        setPrivateSpaceSettingsAvailable(psSettingsIntent != null);
-    }
-
-    private void setPreInstalledSystemPackages() {
-        Preconditions.assertNonUiThread();
-        if (getProfileUser() != null) {
-            mPreInstalledSystemPackages = new HashSet<>(ApiWrapper
-                    .getPreInstalledSystemPackages(mAllApps.getContext(), getProfileUser()));
+        ApiWrapper apiWrapper = ApiWrapper.INSTANCE.get(appContext);
+        UserHandle profileUser = getProfileUser();
+        if (profileUser != null) {
+            mPreInstalledSystemPackages = new HashSet<>(
+                    apiWrapper.getPreInstalledSystemPackages(profileUser));
+            mAppInstallerIntent = apiWrapper
+                    .getAppMarketActivityIntent(BuildConfig.APPLICATION_ID, profileUser);
         }
-    }
-
-    private void setAppInstallerIntent() {
-        Preconditions.assertNonUiThread();
-        if (getProfileUser() != null) {
-            mAppInstallerIntent = ApiWrapper.getAppMarketActivityIntent(mAllApps.getContext(),
-                    BuildConfig.APPLICATION_ID, getProfileUser());
-        }
+        setPrivateSpaceSettingsAvailable(apiWrapper.getPrivateSpaceSettingsIntent() != null);
     }
 
     @VisibleForTesting
diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java
index 8e82d89..0e955ad 100644
--- a/src/com/android/launcher3/apppairs/AppPairIcon.java
+++ b/src/com/android/launcher3/apppairs/AppPairIcon.java
@@ -110,22 +110,42 @@
         // For some reason, app icons have setIncludeFontPadding(false) inside folders, so we set it
         // here to match that.
         icon.mAppPairName.setIncludeFontPadding(container != DISPLAY_FOLDER);
-        icon.mAppPairName.applyLabel(appPairInfo);
+        // Set title text and accessibility title text.
+        icon.updateTitleAndA11yTitle();
 
-        // Set up accessibility
-        icon.setContentDescription(icon.getAccessibilityTitle(appPairInfo));
         icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
 
         return icon;
     }
 
     /**
-     * Returns a formatted accessibility title for app pairs.
+     * Updates the title and a11y title of the app pair. Called on creation and when packages
+     * change, to reflect app name changes or user language changes.
      */
-    public String getAccessibilityTitle(AppPairInfo appPairInfo) {
-        CharSequence app1 = appPairInfo.getFirstApp().title;
-        CharSequence app2 = appPairInfo.getSecondApp().title;
-        return getContext().getString(R.string.app_pair_name_format, app1, app2);
+    public void updateTitleAndA11yTitle() {
+        updateTitleAndTextView();
+        updateAccessibilityTitle();
+    }
+
+    /**
+     * Updates AppPairInfo with a formatted app pair title, and sets it on the BubbleTextView.
+     */
+    public void updateTitleAndTextView() {
+        CharSequence newTitle = getInfo().generateTitle(getContext());
+        mAppPairName.setText(newTitle);
+    }
+
+    /**
+     * Updates the accessibility title with a formatted string template.
+     */
+    public void updateAccessibilityTitle() {
+        CharSequence app1 = getInfo().getFirstApp().title;
+        CharSequence app2 = getInfo().getSecondApp().title;
+        String a11yTitle = getContext().getString(R.string.app_pair_name_format, app1, app2);
+        setContentDescription(
+                getInfo().shouldDrawAsDisabled(getContext())
+                        ? getContext().getString(R.string.disabled_app_label, a11yTitle)
+                        : a11yTitle);
     }
 
     // Required for DraggableView
@@ -200,6 +220,7 @@
         // If either of the app pair icons return true on the predicate (i.e. in the list of
         // updated apps), redraw the icon graphic (icon background and both icons).
         if (getInfo().anyMatch(itemCheck)) {
+            updateTitleAndA11yTitle();
             mIconGraphic.redraw();
         }
     }
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
index a974133..7809102 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -42,13 +42,7 @@
     private val TAG = "AppPairIconGraphic"
 
     companion object {
-        /**
-         * Composes a drawable for this icon, consisting of a background and 2 app icons. The app
-         * pair will draw as "disabled" if either of the following is true:
-         * 1) One of the member WorkspaceItemInfos is disabled (i.e. the app software itself is
-         *    paused or can't be launched for some other reason).
-         * 2) One of the member apps can't be launched due to screen size requirements.
-         */
+        /** Composes a drawable for this icon, consisting of a background and 2 app icons. */
         @JvmStatic
         fun composeDrawable(
             appPairInfo: AppPairInfo,
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index e476138..d6ce2b3 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -17,14 +17,6 @@
 package com.android.launcher3.config;
 
 import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_EXTRA_TOUCH_WIDTH_DP;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
 import static com.android.launcher3.config.FeatureFlags.FlagState.DISABLED;
 import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
 import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
@@ -38,10 +30,8 @@
 
 import com.android.launcher3.BuildConfig;
 import com.android.launcher3.Flags;
-import com.android.launcher3.uioverrides.flags.FlagsFactory;
 
 import java.util.function.Predicate;
-import java.util.function.ToIntFunction;
 
 /**
  * Defines a set of flags used to control various launcher behaviors.
@@ -52,8 +42,6 @@
 
     @VisibleForTesting
     public static Predicate<BooleanFlag> sBooleanReader = f -> f.mCurrentValue;
-    @VisibleForTesting
-    public static ToIntFunction<IntFlag> sIntReader = f -> f.mCurrentValue;
 
     private FeatureFlags() { }
 
@@ -130,42 +118,6 @@
             getDebugFlag(275132633, "ENABLE_ALL_APPS_FROM_OVERVIEW", DISABLED,
                     "Allow entering All Apps from Overview (e.g. long swipe up from app)");
 
-    public static final BooleanFlag CUSTOM_LPNH_THRESHOLDS =
-            getReleaseFlag(301680992, "CUSTOM_LPNH_THRESHOLDS", ENABLED,
-                    "Add dev options and server side control to customize the LPNH "
-                            + "trigger slop and milliseconds");
-
-    public static final BooleanFlag CUSTOM_LPH_THRESHOLDS = getReleaseFlag(331800576,
-            "CUSTOM_LPH_THRESHOLDS", DISABLED,
-            "Server side control to customize LPH timeout and touch slop");
-
-    public static final BooleanFlag OVERRIDE_LPNH_LPH_THRESHOLDS = getReleaseFlag(331799727,
-            "OVERRIDE_LPNH_LPH_THRESHOLDS", DISABLED,
-            "Enable AGSA override for LPNH and LPH timeout and touch slop");
-
-    public static final BooleanFlag ANIMATE_LPNH =
-            getReleaseFlag(308693847, "ANIMATE_LPNH", TEAMFOOD,
-                    "Animates navbar when long pressing");
-
-    public static final BooleanFlag SHRINK_NAV_HANDLE_ON_PRESS =
-            getReleaseFlag(314158312, "SHRINK_NAV_HANDLE_ON_PRESS", DISABLED,
-                    "Shrinks navbar when long pressing if ANIMATE_LPNH is enabled");
-
-    public static final IntFlag LPNH_SLOP_PERCENTAGE =
-            FlagsFactory.getIntFlag(301680992, "LPNH_SLOP_PERCENTAGE", 100,
-                    "Controls touch slop percentage for lpnh",
-                    LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE);
-
-    public static final IntFlag LPNH_EXTRA_TOUCH_WIDTH_DP =
-            FlagsFactory.getIntFlag(301680992, "LPNH_EXTRA_TOUCH_WIDTH_DP", 0,
-                    "Controls extra dp on the nav bar sides to trigger LPNH."
-                            + " Can be negative for a smaller touch region.",
-                    LONG_PRESS_NAV_HANDLE_EXTRA_TOUCH_WIDTH_DP);
-
-    public static final IntFlag LPNH_TIMEOUT_MS =
-            FlagsFactory.getIntFlag(301680992, "LPNH_TIMEOUT_MS", 450,
-                    "Controls lpnh timeout in milliseconds", LONG_PRESS_NAV_HANDLE_TIMEOUT_MS);
-
     public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = getReleaseFlag(
             270394468, "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", ENABLED,
             "Enable option to show keyboard when going to all-apps");
@@ -296,49 +248,6 @@
             "INJECT_FALLBACK_APP_CORPUS_RESULTS", DISABLED,
             "Inject fallback app corpus result when AiAi fails to return it.");
 
-    public static final BooleanFlag ENABLE_LONG_PRESS_NAV_HANDLE =
-            getReleaseFlag(299682306, "ENABLE_LONG_PRESS_NAV_HANDLE", ENABLED,
-                    "Enables long pressing on the bottom bar nav handle to trigger events.");
-
-    public static final BooleanFlag ENABLE_SEARCH_HAPTIC_HINT =
-            getReleaseFlag(314005131, "ENABLE_SEARCH_HAPTIC_HINT", ENABLED,
-                    "Enables haptic hint while long pressing on the bottom bar nav handle.");
-
-    public static final BooleanFlag ENABLE_SEARCH_HAPTIC_COMMIT =
-            getReleaseFlag(314005577, "ENABLE_SEARCH_HAPTIC_COMMIT", ENABLED,
-                    "Enables haptic hint at end of long pressing on the bottom bar nav handle.");
-
-    public static final IntFlag LPNH_HAPTIC_HINT_START_SCALE_PERCENT =
-            FlagsFactory.getIntFlag(309972570,
-                    "LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0,
-                    "Haptic hint start scale.",
-                    LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT);
-
-    public static final IntFlag LPNH_HAPTIC_HINT_END_SCALE_PERCENT =
-            FlagsFactory.getIntFlag(309972570,
-                    "LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100,
-                    "Haptic hint end scale.", LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT);
-
-    public static final IntFlag LPNH_HAPTIC_HINT_SCALE_EXPONENT =
-            FlagsFactory.getIntFlag(309972570,
-                    "LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1,
-                    "Haptic hint scale exponent.",
-                    LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT);
-
-    public static final IntFlag LPNH_HAPTIC_HINT_ITERATIONS =
-            FlagsFactory.getIntFlag(309972570, "LPNH_HAPTIC_HINT_ITERATIONS",
-                    50,
-                    "Haptic hint number of iterations.",
-                    LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS);
-
-    public static final BooleanFlag ENABLE_LPNH_DEEP_PRESS =
-            getReleaseFlag(310952290, "ENABLE_LPNH_DEEP_PRESS", ENABLED,
-                    "Long press of nav handle is instantly triggered if deep press is detected.");
-
-    public static final IntFlag LPNH_HAPTIC_HINT_DELAY =
-            FlagsFactory.getIntFlag(309972570, "LPNH_HAPTIC_HINT_DELAY", 0,
-                    "Delay before haptic hint starts.", LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY);
-
     // TODO(Block 17): Clean up flags
     // Aconfig migration complete for ENABLE_TASKBAR_PINNING.
     private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(296231746,
@@ -515,22 +424,6 @@
     }
 
     /**
-     * Class representing an integer flag
-     */
-    public static class IntFlag {
-
-        private final int mCurrentValue;
-
-        public IntFlag(int currentValue) {
-            mCurrentValue = currentValue;
-        }
-
-        public int get() {
-            return sIntReader.applyAsInt(this);
-        }
-    }
-
-    /**
      * Enabled state for a flag
      */
     public enum FlagState {
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 29aa216..05fdcef 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -67,7 +67,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.pm.PinRequestHelper;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.views.AbstractSlideInView;
@@ -259,7 +259,8 @@
                         .setPackage(getPackageName())
                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         Launcher.ACTIVITY_TRACKER.registerCallback(listener, "AddItemActivity.onLongClick");
-        startActivity(homeIntent, ApiWrapper.createFadeOutAnimOptions(this).toBundle());
+        startActivity(homeIntent,
+                ApiWrapper.INSTANCE.get(this).createFadeOutAnimOptions().toBundle());
         logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_DRAGGED);
         mFinishOnPause = true;
         return false;
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 1633eba..af704a8 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -51,6 +51,7 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.core.util.Pair;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
@@ -248,7 +249,7 @@
     @SuppressWarnings("NewApi")
     public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
             LauncherActivityInfo activityInfo, boolean useLowResIcon) {
-        boolean isAppArchived = Utilities.enableSupportForArchiving() && activityInfo != null
+        boolean isAppArchived = Flags.enableSupportForArchiving() && activityInfo != null
                 && activityInfo.getActivityInfo().isArchived;
         // If we already have activity info, no need to use package icon
         getTitleAndIcon(info, () -> activityInfo, isAppArchived, useLowResIcon,
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index e8f8ae2..441bbb5 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -218,6 +218,9 @@
         @UiEvent(doc = "User tapped on free form icon on a task menu.")
         LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP(519),
 
+        @UiEvent(doc = "User tapped on desktop icon on a task menu.")
+        LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP(1706),
+
         @UiEvent(doc = "User tapped on pause app system shortcut.")
         LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP(521),
 
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 8659471..8c5ea79 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -33,6 +33,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.AppFilter;
+import com.android.launcher3.Flags;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.icons.IconCache;
@@ -330,7 +331,7 @@
                             PackageManagerHelper.getLoadingProgress(info),
                             PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
                     applicationInfo.intent = launchIntent;
-                    if (Utilities.enableSupportForArchiving()) {
+                    if (Flags.enableSupportForArchiving()) {
                         // In case an app is archived, the respective item flag corresponding to
                         // archiving should also be applied during package updates
                         if (info.getActivityInfo().isArchived) {
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index d350879..90aba2a 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -40,6 +40,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
@@ -300,7 +301,7 @@
                     } else {
                         lai = laiList.get(0);
                         si.intent = makeLaunchIntent(lai);
-                        if (Utilities.enableSupportForArchiving()
+                        if (Flags.enableSupportForArchiving()
                                 && lai.getActivityInfo().isArchived) {
                             si.runtimeStatusFlags |= FLAG_ARCHIVED;
                         }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index e0ced83..ac4c087 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -421,7 +421,7 @@
 
             final HashMap<PackageUserKey, SessionInfo> installingPkgs =
                     mSessionHelper.getActiveSessions();
-            if (Utilities.enableSupportForArchiving()) {
+            if (Flags.enableSupportForArchiving()) {
                 mInstallingPkgsCached = installingPkgs;
             }
             installingPkgs.forEach(mApp.getIconCache()::updateSessionCache);
@@ -695,7 +695,7 @@
             for (int i = 0; i < apps.size(); i++) {
                 LauncherActivityInfo app = apps.get(i);
                 AppInfo appInfo = new AppInfo(app, mUserCache.getUserInfo(user), quietMode);
-                if (Utilities.enableSupportForArchiving() && app.getApplicationInfo().isArchived) {
+                if (Flags.enableSupportForArchiving() && app.getApplicationInfo().isArchived) {
                     // For archived apps, include progress info in case there is a pending
                     // install session post restart of device.
                     String appPackageName = app.getApplicationInfo().packageName;
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index ea1ae2e..1923065 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -48,7 +48,7 @@
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -276,7 +276,7 @@
                                     PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
                             // In case an app is archived, we need to make sure that archived state
                             // in WorkspaceItemInfo is refreshed.
-                            if (Utilities.enableSupportForArchiving() && !activities.isEmpty()) {
+                            if (Flags.enableSupportForArchiving() && !activities.isEmpty()) {
                                 boolean newArchivalState = activities.get(
                                         0).getActivityInfo().isArchived;
                                 if (newArchivalState != si.isArchived()) {
@@ -286,7 +286,7 @@
                             }
                             if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
                                 if (activities != null && !activities.isEmpty()) {
-                                    si.status = ApiWrapper
+                                    si.status = ApiWrapper.INSTANCE.get(context)
                                             .isNonResizeableActivity(activities.get(0))
                                             ? si.status | WorkspaceItemInfo.FLAG_NON_RESIZEABLE
                                             : si.status & ~WorkspaceItemInfo.FLAG_NON_RESIZEABLE;
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index d27be72..ee45c0f 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -26,11 +26,13 @@
 import android.text.TextUtils
 import android.util.Log
 import android.util.LongSparseArray
+import com.android.launcher3.Flags
 import com.android.launcher3.InvariantDeviceProfile
 import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherSettings.Favorites
 import com.android.launcher3.Utilities
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
+import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.logging.FileLog
 import com.android.launcher3.model.data.AppPairInfo
 import com.android.launcher3.model.data.FolderInfo
@@ -40,7 +42,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.pm.PackageInstallInfo
 import com.android.launcher3.shortcuts.ShortcutKey
-import com.android.launcher3.uioverrides.ApiWrapper
+import com.android.launcher3.util.ApiWrapper
 import com.android.launcher3.util.ComponentKey
 import com.android.launcher3.util.PackageManagerHelper
 import com.android.launcher3.util.PackageUserKey
@@ -324,7 +326,7 @@
             }
             val activityInfo = c.launcherActivityInfo
             if (activityInfo != null) {
-                if (ApiWrapper.isNonResizeableActivity(activityInfo)) {
+                if (ApiWrapper.INSTANCE.get(app.context).isNonResizeableActivity(activityInfo)) {
                     info.status = info.status or WorkspaceItemInfo.FLAG_NON_RESIZEABLE
                 }
                 info.setProgressLevel(
@@ -334,7 +336,7 @@
             }
             if (
                 (c.restoreFlag != 0 ||
-                    Utilities.enableSupportForArchiving() &&
+                    Flags.enableSupportForArchiving() &&
                         activityInfo != null &&
                         activityInfo.applicationInfo.isArchived) && !TextUtils.isEmpty(targetPkg)
             ) {
@@ -346,7 +348,7 @@
                             ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE.inv()
                 } else if (
                     activityInfo == null ||
-                        (Utilities.enableSupportForArchiving() &&
+                        (Flags.enableSupportForArchiving() &&
                             activityInfo.applicationInfo.isArchived)
                 ) {
                     // For archived apps, include progress info in case there is
@@ -372,6 +374,12 @@
         // If we generated a placeholder Folder before this point, it may need to be replaced with
         // an app pair.
         if (c.itemType == Favorites.ITEM_TYPE_APP_PAIR && collection is FolderInfo) {
+            if (!FeatureFlags.enableAppPairs()) {
+                // If app pairs are not enabled, stop loading.
+                Log.e(TAG, "app pairs flag is off, did not load app pair")
+                return
+            }
+
             val folderInfo: FolderInfo = collection
             val newAppPair = AppPairInfo()
             // Move the placeholder's contents over to the new app pair.
@@ -472,7 +480,7 @@
                         !isSafeMode &&
                         (si == null) &&
                         (lapi == null) &&
-                        !(Utilities.enableSupportForArchiving() &&
+                        !(Flags.enableSupportForArchiving() &&
                             pmHelper.isAppArchived(component.packageName))
                 ) {
                     // Restore never started
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index 210d720..93ba619 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -181,7 +181,7 @@
         if (PackageManagerHelper.isAppSuspended(appInfo)) {
             info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
         }
-        if (Utilities.enableSupportForArchiving() && lai.getActivityInfo().isArchived) {
+        if (Flags.enableSupportForArchiving() && lai.getActivityInfo().isArchived) {
             info.runtimeStatusFlags |= FLAG_ARCHIVED;
         }
         info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
index 63c77bb..fad365c 100644
--- a/src/com/android/launcher3/model/data/AppPairInfo.kt
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import com.android.launcher3.LauncherSettings
+import com.android.launcher3.R
 import com.android.launcher3.icons.IconCache
 import com.android.launcher3.logger.LauncherAtom
 import com.android.launcher3.views.ActivityContext
@@ -81,6 +82,24 @@
         }
     }
 
+    /**
+     * App pairs will draw as "disabled" if either of the following is true:
+     * 1) One of the member WorkspaceItemInfos is disabled (i.e. the app software itself is paused
+     *    or can't be launched for some other reason).
+     * 2) One of the member apps can't be launched due to screen size requirements.
+     */
+    fun shouldDrawAsDisabled(context: Context): Boolean {
+        return isDisabled || !isLaunchable(context)
+    }
+
+    /** Generates a default title for the app pair and sets it. */
+    fun generateTitle(context: Context): CharSequence? {
+        val app1: CharSequence? = getFirstApp().title
+        val app2: CharSequence? = getSecondApp().title
+        title = context.getString(R.string.app_pair_default_title, app1, app2)
+        return title
+    }
+
     /** Generates an ItemInfo for logging. */
     override fun buildProto(cInfo: CollectionInfo?): LauncherAtom.ItemInfo {
         val appPairIcon = LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size)
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index be3aa10..3a74ff2 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -22,13 +22,14 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags;
 import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.pm.PackageInstallInfo;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
 
 /**
  * Represents an ItemInfo which also holds an icon.
@@ -162,7 +163,7 @@
      * Returns true if the app corresponding to the item is archived.
      */
     public boolean isArchived() {
-        if (!Utilities.enableSupportForArchiving()) {
+        if (!Flags.enableSupportForArchiving()) {
             return false;
         }
         return (runtimeStatusFlags & FLAG_ARCHIVED) != 0;
@@ -251,8 +252,8 @@
         String targetPackage = getTargetPackage();
 
         return targetPackage != null
-                ? ApiWrapper.getAppMarketActivityIntent(
-                context, targetPackage, Process.myUserHandle())
+                ? ApiWrapper.INSTANCE.get(context).getAppMarketActivityIntent(
+                        targetPackage, Process.myUserHandle())
                 : null;
     }
 
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index 9917ad7..5ae7003 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -32,7 +32,7 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.ContentWriter;
 
 import java.util.Arrays;
@@ -201,7 +201,7 @@
             runtimeStatusFlags &= ~FLAG_DISABLED_VERSION_LOWER;
         }
 
-        Person[] persons = ApiWrapper.getPersons(shortcutInfo);
+        Person[] persons = ApiWrapper.INSTANCE.get(context).getPersons(shortcutInfo);
         personKeys = persons.length == 0 ? Utilities.EMPTY_STRING_ARRAY
             : Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new);
     }
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index f3769d5..4a3318e 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -29,6 +29,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.SessionCommitReceiver;
 import com.android.launcher3.Utilities;
@@ -217,7 +218,7 @@
                 && !promiseIconAddedForId(sessionInfo.getSessionId())) {
             // In case of unarchival, we do not want to add a workspace promise icon if one is
             // not already present. For general app installations however, we do support it.
-            if (!Utilities.enableSupportForArchiving() || !sessionInfo.isUnarchival()) {
+            if (!Flags.enableSupportForArchiving() || !sessionInfo.isUnarchival()) {
                 FileLog.d(LOG, "Adding package name to install queue: "
                         + sessionInfo.getAppPackageName());
 
@@ -232,7 +233,7 @@
 
     public boolean verifySessionInfo(@Nullable final PackageInstaller.SessionInfo sessionInfo) {
         // For archived apps we always want to show promise icons and the checks below don't apply.
-        if (Utilities.enableSupportForArchiving() && sessionInfo != null
+        if (Flags.enableSupportForArchiving() && sessionInfo != null
                 && sessionInfo.isUnarchival()) {
             return true;
         }
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index eacbc11..24d58f3 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -31,6 +31,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.PackageUserKey;
 
@@ -80,7 +81,7 @@
 
         helper.tryQueuePromiseAppIcon(sessionInfo);
 
-        if (Utilities.enableSupportForArchiving() && sessionInfo != null
+        if (Flags.enableSupportForArchiving() && sessionInfo != null
                 && sessionInfo.isUnarchival()) {
             // For archived apps, icon could already be present on the workspace. To make sure
             // the icon state is updated, we send a change event.
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index 032de31..185e0b5 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.pm;
 
 import static com.android.launcher3.Utilities.ATLEAST_U;
-import static com.android.launcher3.uioverrides.ApiWrapper.queryAllUsers;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.content.Context;
@@ -34,6 +33,7 @@
 
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.UserBadgeDrawable;
+import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SafeCloseable;
@@ -119,7 +119,7 @@
 
     @WorkerThread
     private void updateCache() {
-        mUserToSerialMap = queryAllUsers(mContext);
+        mUserToSerialMap = ApiWrapper.INSTANCE.get(mContext).queryAllUsers();
     }
 
     /**
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 0af7e67..f56d732 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -22,6 +22,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.AbstractFloatingViewHelper;
 import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.SecondaryDropTarget;
@@ -31,7 +32,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -61,23 +62,23 @@
     protected final ItemInfo mItemInfo;
     protected final View mOriginalView;
 
+    private final AbstractFloatingViewHelper mAbstractFloatingViewHelper;
+
     public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo,
             View originalView) {
+        this(iconResId, labelResId, target, itemInfo, originalView,
+                new AbstractFloatingViewHelper());
+    }
+
+    public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo,
+            View originalView, AbstractFloatingViewHelper abstractFloatingViewHelper) {
         mIconResId = iconResId;
         mLabelResId = labelResId;
         mAccessibilityActionId = labelResId;
         mTarget = target;
         mItemInfo = itemInfo;
         mOriginalView = originalView;
-    }
-
-    public SystemShortcut(SystemShortcut<T> other) {
-        mIconResId = other.mIconResId;
-        mLabelResId = other.mLabelResId;
-        mAccessibilityActionId = other.mAccessibilityActionId;
-        mTarget = other.mTarget;
-        mItemInfo = other.mItemInfo;
-        mOriginalView = other.mOriginalView;
+        mAbstractFloatingViewHelper = abstractFloatingViewHelper;
     }
 
     public void setIconAndLabelFor(View iconView, TextView labelView) {
@@ -178,7 +179,7 @@
 
         @Override
         public void onClick(View view) {
-            dismissTaskMenuView(mTarget);
+            dismissTaskMenuView();
             Rect sourceBounds = Utilities.getViewBounds(view);
             new PackageManagerHelper(view.getContext()).startDetailsActivityForInfo(
                     mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
@@ -259,10 +260,8 @@
         @Override
         public void onClick(View view) {
             Intent intent =
-                    ApiWrapper.getAppMarketActivityIntent(
-                            view.getContext(),
-                            mItemInfo.getTargetComponent().getPackageName(),
-                            mSpaceUser);
+                    ApiWrapper.INSTANCE.get(view.getContext()).getAppMarketActivityIntent(
+                            mItemInfo.getTargetComponent().getPackageName(), mSpaceUser);
             mTarget.startActivitySafely(view, intent, mItemInfo);
             AbstractFloatingView.closeAllOpenViews(mTarget);
             mTarget.getStatsLogManager()
@@ -303,9 +302,8 @@
 
         @Override
         public void onClick(View view) {
-            Intent intent = ApiWrapper.getAppMarketActivityIntent(view.getContext(),
-                    mItemInfo.getTargetComponent().getPackageName(),
-                    Process.myUserHandle());
+            Intent intent = ApiWrapper.INSTANCE.get(view.getContext()).getAppMarketActivityIntent(
+                    mItemInfo.getTargetComponent().getPackageName(), Process.myUserHandle());
             mTarget.startActivitySafely(view, intent, mItemInfo);
             AbstractFloatingView.closeAllOpenViews(mTarget);
         }
@@ -327,7 +325,7 @@
 
         @Override
         public void onClick(View view) {
-            dismissTaskMenuView(mTarget);
+            dismissTaskMenuView();
             mTarget.getStatsLogManager().logger()
                     .withItemInfo(mItemInfo)
                     .log(LAUNCHER_SYSTEM_SHORTCUT_DONT_SUGGEST_APP_TAP);
@@ -370,7 +368,7 @@
 
         @Override
         public void onClick(View view) {
-            dismissTaskMenuView(mTarget);
+            dismissTaskMenuView();
             SecondaryDropTarget.performUninstall(view.getContext(), mComponentName, mItemInfo);
             mTarget.getStatsLogManager()
                     .logger()
@@ -379,8 +377,8 @@
         }
     }
 
-    public static <T extends ActivityContext> void dismissTaskMenuView(T activity) {
-        AbstractFloatingView.closeOpenViews(activity, true,
+    protected void dismissTaskMenuView() {
+        mAbstractFloatingViewHelper.closeOpenViews(mTarget, true,
                 AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
     }
 }
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index b4e6365..d702e51 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -70,7 +70,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.ContentWriter;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.LogConfig;
@@ -560,9 +560,8 @@
 
     protected static void maybeOverrideShortcuts(Context context, ModelDbController controller,
             SQLiteDatabase db, long currentUser) {
-        Map<String, LauncherActivityInfo> activityOverrides = ApiWrapper.getActivityOverrides(
-                context);
-
+        Map<String, LauncherActivityInfo> activityOverrides =
+                ApiWrapper.INSTANCE.get(context).getActivityOverrides();
         if (activityOverrides == null || activityOverrides.isEmpty()) {
             return;
         }
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 50df775..2a9ebbd 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -41,6 +41,7 @@
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.BuildConfig;
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
@@ -63,7 +64,7 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.launcher3.views.Snackbar;
@@ -234,7 +235,7 @@
                 }
             }
             // Fallback to using custom market intent.
-            Intent intent = ApiWrapper.getAppMarketActivityIntent(launcher,
+            Intent intent = ApiWrapper.INSTANCE.get(launcher).getAppMarketActivityIntent(
                     packageName, Process.myUserHandle());
             launcher.startActivitySafely(v, intent, item);
         };
@@ -346,7 +347,7 @@
 
         // Check for abandoned promise
         if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()
-                && (!Utilities.enableSupportForArchiving() || !shortcut.isArchived())) {
+                && (!Flags.enableSupportForArchiving() || !shortcut.isArchived())) {
             String packageName = shortcut.getIntent().getComponent() != null
                     ? shortcut.getIntent().getComponent().getPackageName()
                     : shortcut.getIntent().getPackage();
@@ -372,12 +373,12 @@
         if (item instanceof ItemInfoWithIcon itemInfoWithIcon) {
             if ((itemInfoWithIcon.runtimeStatusFlags
                     & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
-                intent = ApiWrapper.getAppMarketActivityIntent(launcher,
+                intent = ApiWrapper.INSTANCE.get(launcher).getAppMarketActivityIntent(
                         itemInfoWithIcon.getTargetComponent().getPackageName(),
                         Process.myUserHandle());
             } else if (itemInfoWithIcon.itemType
                     == LauncherSettings.Favorites.ITEM_TYPE_PRIVATE_SPACE_INSTALL_APP_BUTTON) {
-                intent = ApiWrapper.getAppMarketActivityIntent(launcher,
+                intent = ApiWrapper.INSTANCE.get(launcher).getAppMarketActivityIntent(
                         BuildConfig.APPLICATION_ID,
                         launcher.getAppsView().getPrivateProfileManager().getProfileUser());
                 launcher.getStatsLogManager().logger().log(
diff --git a/src_no_quickstep/com/android/launcher3/uioverrides/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java
similarity index 67%
rename from src_no_quickstep/com/android/launcher3/uioverrides/ApiWrapper.java
rename to src/com/android/launcher3/util/ApiWrapper.java
index 90271c1..6429a43 100644
--- a/src_no_quickstep/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src/com/android/launcher3/util/ApiWrapper.java
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.uioverrides;
+package com.android.launcher3.util;
+
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 
 import android.app.ActivityOptions;
 import android.app.Person;
@@ -28,10 +30,12 @@
 import android.os.UserManager;
 import android.util.ArrayMap;
 
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.UserIconInfo;
+import androidx.annotation.Nullable;
 
-import java.util.ArrayList;
+import com.android.launcher3.BuildConfig;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -39,30 +43,40 @@
 /**
  * A wrapper for the hidden API calls
  */
-public class ApiWrapper {
+public class ApiWrapper implements ResourceBasedOverride, SafeCloseable {
 
-    public static final boolean TASKBAR_DRAWN_IN_PROCESS = false;
+    public static final MainThreadInitializedObject<ApiWrapper> INSTANCE =
+            forOverride(ApiWrapper.class, R.string.api_wrapper_class);
 
-    public static Person[] getPersons(ShortcutInfo si) {
+    protected final Context mContext;
+
+    public ApiWrapper(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Returns the list of persons associated with the provided shortcut info
+     */
+    public Person[] getPersons(ShortcutInfo si) {
         return Utilities.EMPTY_PERSON_ARRAY;
     }
 
-    public static Map<String, LauncherActivityInfo> getActivityOverrides(Context context) {
+    public Map<String, LauncherActivityInfo> getActivityOverrides() {
         return Collections.emptyMap();
     }
 
     /**
      * Creates an ActivityOptions to play fade-out animation on closing targets
      */
-    public static ActivityOptions createFadeOutAnimOptions(Context context) {
-        return ActivityOptions.makeCustomAnimation(context, 0, android.R.anim.fade_out);
+    public ActivityOptions createFadeOutAnimOptions() {
+        return ActivityOptions.makeCustomAnimation(mContext, 0, android.R.anim.fade_out);
     }
 
     /**
      * Returns a map of all users on the device to their corresponding UI properties
      */
-    public static Map<UserHandle, UserIconInfo> queryAllUsers(Context context) {
-        UserManager um = context.getSystemService(UserManager.class);
+    public Map<UserHandle, UserIconInfo> queryAllUsers() {
+        UserManager um = mContext.getSystemService(UserManager.class);
         Map<UserHandle, UserIconInfo> users = new ArrayMap<>();
         List<UserHandle> usersActual = um.getUserProfiles();
         if (usersActual != null) {
@@ -72,7 +86,7 @@
                 // Simple check to check if the provided user is work profile
                 // TODO: Migrate to a better platform API
                 NoopDrawable d = new NoopDrawable();
-                boolean isWork = (d != context.getPackageManager().getUserBadgedIcon(d, user));
+                boolean isWork = (d != mContext.getPackageManager().getUserBadgedIcon(d, user));
                 UserIconInfo info = new UserIconInfo(
                         user,
                         isWork ? UserIconInfo.TYPE_WORK : UserIconInfo.TYPE_MAIN,
@@ -87,16 +101,15 @@
      * Returns the list of the system packages that are installed at user creation.
      * An empty list denotes that all system packages are installed for that user at creation.
      */
-    public static List<String> getPreInstalledSystemPackages(Context context, UserHandle user) {
-        return new ArrayList<>();
+    public List<String> getPreInstalledSystemPackages(UserHandle user) {
+        return Collections.emptyList();
     }
 
     /**
      * Returns an intent which can be used to start the App Market activity (Installer
      * Activity).
      */
-    public static Intent getAppMarketActivityIntent(Context context, String packageName,
-            UserHandle user) {
+    public Intent getAppMarketActivityIntent(String packageName, UserHandle user) {
         return new Intent(Intent.ACTION_VIEW)
                 .setData(new Uri.Builder()
                         .scheme("market")
@@ -104,24 +117,27 @@
                         .appendQueryParameter("id", packageName)
                         .build())
                 .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app")
-                        .authority(context.getPackageName()).build());
+                        .authority(BuildConfig.APPLICATION_ID).build());
     }
 
     /**
      * Returns an intent which can be used to open Private Space Settings.
      */
-    public static Intent getPrivateSpaceSettingsIntent(Context context) {
+    @Nullable
+    public Intent getPrivateSpaceSettingsIntent() {
         return null;
     }
 
     /**
      * Checks if an activity is flagged as non-resizeable.
      */
-    public static boolean isNonResizeableActivity(LauncherActivityInfo lai) {
+    public boolean isNonResizeableActivity(LauncherActivityInfo lai) {
         // Overridden in quickstep
         return false;
     }
 
+    @Override
+    public void close() { }
 
     private static class NoopDrawable extends ColorDrawable {
         @Override
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 316506a..608bed7 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.util;
 
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
+
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
@@ -38,6 +40,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -46,7 +49,6 @@
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.uioverrides.ApiWrapper;
 
 import java.util.List;
 import java.util.Objects;
@@ -109,7 +111,7 @@
     @SuppressWarnings("NewApi")
     public boolean isAppArchivedForUser(@NonNull final String packageName,
             @NonNull final UserHandle user) {
-        if (!Utilities.enableSupportForArchiving()) {
+        if (!Flags.enableSupportForArchiving()) {
             return false;
         }
         final ApplicationInfo info = getApplicationInfo(
@@ -169,11 +171,9 @@
      * Starts the details activity for {@code info}
      */
     public void startDetailsActivityForInfo(ItemInfo info, Rect sourceBounds, Bundle opts) {
-        if (info instanceof ItemInfoWithIcon
-                && (((ItemInfoWithIcon) info).runtimeStatusFlags
-                & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
-            ItemInfoWithIcon appInfo = (ItemInfoWithIcon) info;
-            mContext.startActivity(ApiWrapper.getAppMarketActivityIntent(mContext,
+        if (info instanceof ItemInfoWithIcon appInfo
+                && (appInfo.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0) {
+            mContext.startActivity(ApiWrapper.INSTANCE.get(mContext).getAppMarketActivityIntent(
                     appInfo.getTargetComponent().getPackageName(), Process.myUserHandle()));
             return;
         }
@@ -274,6 +274,6 @@
     @SuppressWarnings("NewApi")
     private boolean isPackageInstalledOrArchived(ApplicationInfo info) {
         return (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0 || (
-                Utilities.enableSupportForArchiving() && info.isArchived);
+                Flags.enableSupportForArchiving() && info.isArchived);
     }
 }
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index e1695e9..cd60c1d 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -19,11 +19,6 @@
 import static android.os.VibrationEffect.createPredefined;
 import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
 
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_DELAY;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_END_SCALE_PERCENT;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_ITERATIONS;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_SCALE_EXPONENT;
-import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_START_SCALE_PERCENT;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
@@ -40,7 +35,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 
 /**
  * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
@@ -71,9 +65,6 @@
     @Nullable
     private final VibrationEffect mBumpEffect;
 
-    @Nullable
-    private final VibrationEffect mSearchEffect;
-
     private long mLastDragTime;
     private final int mThresholdUntilNextDragCallMillis;
 
@@ -133,25 +124,6 @@
             mBumpEffect = null;
             mThresholdUntilNextDragCallMillis = 0;
         }
-
-        if (mVibrator.areAllPrimitivesSupported(
-                VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
-                VibrationEffect.Composition.PRIMITIVE_TICK)) {
-            if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get()) {
-                mSearchEffect = VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f)
-                        .compose();
-            } else {
-                // quiet ramp, short pause, then sharp tick
-                mSearchEffect = VibrationEffect.startComposition()
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
-                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50)
-                        .compose();
-            }
-        } else {
-            // fallback for devices without composition support
-            mSearchEffect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK);
-        }
     }
 
     /**
@@ -233,13 +205,6 @@
         }
     }
 
-    /** Indicates that search has been invoked. */
-    public void vibrateForSearch() {
-        if (mSearchEffect != null) {
-            vibrate(mSearchEffect);
-        }
-    }
-
     /** Indicates that Taskbar has been invoked. */
     public void vibrateForTaskbarUnstash() {
         if (Utilities.ATLEAST_S && mVibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)) {
@@ -251,32 +216,4 @@
             vibrate(primitiveLowTickEffect);
         }
     }
-
-    /** Indicates that search will be invoked if the current gesture is maintained. */
-    public void vibrateForSearchHint() {
-        if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get() && Utilities.ATLEAST_S
-                && mVibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)) {
-            float startScale = LPNH_HAPTIC_HINT_START_SCALE_PERCENT.get() / 100f;
-            float endScale = LPNH_HAPTIC_HINT_END_SCALE_PERCENT.get() / 100f;
-            int scaleExponent = LPNH_HAPTIC_HINT_SCALE_EXPONENT.get();
-            int iterations = LPNH_HAPTIC_HINT_ITERATIONS.get();
-            int delayMs = LPNH_HAPTIC_HINT_DELAY.get();
-
-            VibrationEffect.Composition composition = VibrationEffect.startComposition();
-            for (int i = 0; i < iterations; i++) {
-                float t = i / (iterations - 1f);
-                float scale = (float) Math.pow((1 - t) * startScale + t * endScale,
-                        scaleExponent);
-                if (i == 0) {
-                    // Adds a delay before the ramp starts
-                    composition.addPrimitive(PRIMITIVE_LOW_TICK, scale,
-                            delayMs);
-                } else {
-                    composition.addPrimitive(PRIMITIVE_LOW_TICK, scale);
-                }
-            }
-
-            vibrate(composition.compose());
-        }
-    }
 }
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 4a906d3..998191e 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -94,6 +94,13 @@
     }
 
     /**
+     * Returns true if taskbar is drawn in process
+     */
+    public boolean isTaskbarDrawnInProcess() {
+        return mTaskbarDrawnInProcess;
+    }
+
+    /**
      * Returns a map of normalized info of internal displays to estimated window bounds
      * for that display
      */
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
index 255a6d2..4f73e66 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
@@ -18,7 +18,9 @@
 
 import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.os.Bundle;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -31,14 +33,19 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * A {@link PagedView} that displays widget recommendations in categories with dots as paged
@@ -46,6 +53,8 @@
  */
 public final class WidgetRecommendationsView extends PagedView<PageIndicatorDots> {
     private @Px float mAvailableHeight = Float.MAX_VALUE;
+    private static final String INITIALLY_DISPLAYED_WIDGETS_STATE_KEY =
+            "widgetRecommendationsView:mDisplayedWidgets";
     private static final int MAX_CATEGORIES = 3;
     private TextView mRecommendationPageTitle;
     private final List<String> mCategoryTitles = new ArrayList<>();
@@ -57,6 +66,7 @@
     private OnLongClickListener mWidgetCellOnLongClickListener;
     @Nullable
     private OnClickListener mWidgetCellOnClickListener;
+    private Set<ComponentName> mDisplayedWidgets = Collections.emptySet();
 
     public WidgetRecommendationsView(Context context) {
         this(context, /* attrs= */ null);
@@ -76,6 +86,38 @@
         mRecommendationPageTitle = parent.findViewById(R.id.recommendations_page_title);
     }
 
+    /**
+     * Saves the necessary state in the provided bundle. To be called in case of orientation /
+     * other config changes.
+     */
+    public void saveState(Bundle bundle) {
+        // Save the widgets that were displayed, so that, on rotation / fold / unfold, we can
+        // maintain the "initial" set of widgets that user first saw (if they fit).
+        bundle.putParcelableArrayList(INITIALLY_DISPLAYED_WIDGETS_STATE_KEY,
+                new ArrayList<>(mDisplayedWidgets));
+    }
+
+    /**
+     * Restores the state that was saved by the saveState method during orientation / other config
+     * changes.
+     */
+    public void restoreState(Bundle bundle) {
+        ArrayList<ComponentName> componentList;
+        if (Utilities.ATLEAST_T) {
+            componentList = bundle.getParcelableArrayList(
+                    INITIALLY_DISPLAYED_WIDGETS_STATE_KEY, ComponentName.class);
+        } else {
+            componentList = bundle.getParcelableArrayList(
+                    INITIALLY_DISPLAYED_WIDGETS_STATE_KEY);
+        }
+
+        // Restore the "initial" set of widgets that were displayed, so that, on rotation / fold /
+        // unfold, we can maintain the set of widgets that user first saw (if they fit).
+        if (componentList != null) {
+            mDisplayedWidgets = new HashSet<>(componentList);
+        }
+    }
+
     /** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */
     public void setWidgetCellLongClickListener(OnLongClickListener onLongClickListener) {
         mWidgetCellOnLongClickListener = onLongClickListener;
@@ -112,10 +154,18 @@
         this.mAvailableHeight = availableHeight;
         clear();
 
-        int displayedWidgets = maybeDisplayInTable(recommendedWidgets, deviceProfile,
+        Set<ComponentName> displayedWidgets = maybeDisplayInTable(recommendedWidgets,
+                deviceProfile,
                 availableWidth, cellPadding);
+
+        if (mDisplayedWidgets.isEmpty()) {
+            // Save the widgets shown for the first time user opened the picker; so that, they can
+            // be maintained across orientation changes.
+            mDisplayedWidgets = displayedWidgets;
+        }
+
         updateTitleAndIndicator(/* requestedPage= */ 0);
-        return displayedWidgets;
+        return displayedWidgets.size();
     }
 
     /**
@@ -144,20 +194,21 @@
         clear();
 
         int displayedCategories = 0;
-        int totalDisplayedWidgets = 0;
+        Set<ComponentName> allDisplayedWidgets = new HashSet<>();
 
         // Render top MAX_CATEGORIES in separate tables. Each table becomes a page.
         for (Map.Entry<WidgetRecommendationCategory, List<WidgetItem>> entry :
                 new TreeMap<>(recommendations).entrySet()) {
             // If none of the recommendations for the category could fit in the mAvailableHeight, we
             // don't want to add that category; and we look for the next one.
-            int displayedCount = maybeDisplayInTable(entry.getValue(), deviceProfile,
+            Set<ComponentName> displayedWidgetsForCategory = maybeDisplayInTable(entry.getValue(),
+                    deviceProfile,
                     availableWidth, cellPadding);
-            if (displayedCount > 0) {
+            if (!displayedWidgetsForCategory.isEmpty()) {
                 mCategoryTitles.add(
                         context.getResources().getString(entry.getKey().categoryTitleRes));
                 displayedCategories++;
-                totalDisplayedWidgets += displayedCount;
+                allDisplayedWidgets.addAll(displayedWidgetsForCategory);
             }
 
             if (displayedCategories == MAX_CATEGORIES) {
@@ -165,11 +216,17 @@
             }
         }
 
+        if (mDisplayedWidgets.isEmpty()) {
+            // Save the widgets shown for the first time user opened the picker; so that, they can
+            // be maintained across orientation changes.
+            mDisplayedWidgets = allDisplayedWidgets;
+        }
+
         updateTitleAndIndicator(requestedPage);
         // For purpose of recommendations section, we don't want paging dots to be halved in two
         // pane display, so, we always provide isTwoPanels = "false".
         mPageIndicator.setPauseScroll(/*pause=*/false, /*isTwoPanels=*/false);
-        return totalDisplayedWidgets;
+        return allDisplayedWidgets.size();
     }
 
     private void clear() {
@@ -241,20 +298,25 @@
     }
 
     /**
-     * Groups the provided recommendations into rows and displays them in a table if at least one
-     * fits.
-     * <p>Returns false if none of the recommendations could fit.</p>
+     * Groups the provided recommendations into rows and displays ones that fit in a table.
+     * <p>Returns the set of widgets that could fit.</p>
      */
-    private int maybeDisplayInTable(List<WidgetItem> recommendedWidgets,
+    private Set<ComponentName> maybeDisplayInTable(List<WidgetItem> recommendedWidgets,
             DeviceProfile deviceProfile,
             final @Px int availableWidth, final @Px int cellPadding) {
+        List<WidgetItem> filteredRecommendedWidgets = recommendedWidgets;
+        // Show only those widgets that were displayed when user first opened the picker.
+        if (!mDisplayedWidgets.isEmpty()) {
+            filteredRecommendedWidgets = recommendedWidgets.stream().filter(
+                    w -> mDisplayedWidgets.contains(w.componentName)).toList();
+        }
         Context context = getContext();
         LayoutInflater inflater = LayoutInflater.from(context);
 
         // Since we are limited by space, we don't sort recommendations - to show most relevant
         // (if possible).
         List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
-                recommendedWidgets,
+                filteredRecommendedWidgets,
                 context,
                 deviceProfile,
                 availableWidth,
@@ -268,13 +330,17 @@
         recommendationsTable.setWidgetCellOnClickListener(mWidgetCellOnClickListener);
         recommendationsTable.setWidgetCellLongClickListener(mWidgetCellOnLongClickListener);
 
-        int displayedCount = recommendationsTable.setRecommendedWidgets(rows,
+        List<ArrayList<WidgetItem>> displayedItems = recommendationsTable.setRecommendedWidgets(
+                rows,
                 deviceProfile, mAvailableHeight);
-        if (displayedCount > 0) {
+
+        if (!displayedItems.isEmpty()) {
             addView(recommendationsTable);
         }
 
-        return displayedCount;
+        return displayedItems.stream().flatMap(
+                        items -> items.stream().map(w -> w.componentName))
+                .collect(Collectors.toSet());
     }
 
     /** Returns location of a widget cell for displaying the "touch and hold" education tip. */
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 85375ee..c3067a5 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -633,15 +633,15 @@
     }
 
     @Px
-    private float getMaxAvailableHeightForRecommendations() {
+    protected float getMaxAvailableHeightForRecommendations() {
         // There isn't enough space to show recommendations in landscape orientation on phones with
         // a full sheet design. Tablets use a two pane picker.
-        if (!isTwoPane() && mDeviceProfile.isLandscape) {
+        if (mDeviceProfile.isLandscape) {
             return 0f;
         }
 
         return (mDeviceProfile.heightPx - mDeviceProfile.bottomSheetTopPadding)
-                * getRecommendationSectionHeightRatio();
+                * RECOMMENDATION_TABLE_HEIGHT_RATIO;
     }
 
     /** b/209579563: "Widgets" header should be focused first. */
@@ -650,14 +650,6 @@
         return mHeaderTitle;
     }
 
-    /**
-     * Ratio of recommendations section with respect to bottom sheet's height on scale of 0 to 1.
-     */
-    @Px
-    protected float getRecommendationSectionHeightRatio() {
-        return RECOMMENDATION_TABLE_HEIGHT_RATIO;
-    }
-
     private void open(boolean animate) {
         if (animate) {
             if (getPopupContainer().getInsets().bottom > 0) {
@@ -733,6 +725,7 @@
         // picker and calls save/restore hierarchy state. We save the state of recommendations
         // across those updates.
         bundle.putInt(RECOMMENDATIONS_SAVED_STATE_KEY, mRecommendationsCurrentPage);
+        mWidgetRecommendationsView.saveState(bundle);
         SparseArray<Parcelable> superState = new SparseArray<>();
         super.saveHierarchyState(superState);
         bundle.putSparseParcelableArray(SUPER_SAVED_STATE_KEY, superState);
@@ -744,6 +737,7 @@
         Bundle state = (Bundle) sparseArray.get(0);
         mRecommendationsCurrentPage = state.getInt(
                 RECOMMENDATIONS_SAVED_STATE_KEY, /*defaultValue=*/0);
+        mWidgetRecommendationsView.restoreState(state);
         super.restoreHierarchyState(state.getSparseParcelableArray(SUPER_SAVED_STATE_KEY));
     }
 
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 563894d..a565780 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -83,14 +83,15 @@
      * <p>If the content can't fit {@code recommendationTableMaxHeight}, this view will remove a
      * last row from the {@code recommendedWidgets} until it fits or only one row left.
      *
-     * <p>Returns {@code false} if none of the widgets could fit</p>
+     * <p>Returns the list of widgets that could fit</p>
      */
-    public int setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
+    public List<ArrayList<WidgetItem>> setRecommendedWidgets(
+            List<ArrayList<WidgetItem>> recommendedWidgets,
             DeviceProfile deviceProfile, float recommendationTableMaxHeight) {
         List<ArrayList<WidgetItem>> rows = selectRowsThatFitInAvailableHeight(recommendedWidgets,
                 recommendationTableMaxHeight, deviceProfile);
         bindData(rows);
-        return rows.stream().mapToInt(ArrayList::size).sum();
+        return rows;
     }
 
     private void bindData(List<ArrayList<WidgetItem>> recommendationTable) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 1bf813c..14985bf 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -276,8 +276,15 @@
 
     @Override
     @Px
-    protected float getRecommendationSectionHeightRatio() {
-        return RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE;
+    protected float getMaxAvailableHeightForRecommendations() {
+        if (mRecommendedWidgetsCount > 0) {
+            // If widgets were already selected for display, we show them all on orientation change
+            // in a two pane picker
+            return Float.MAX_VALUE;
+        }
+
+        return (mDeviceProfile.heightPx - mDeviceProfile.bottomSheetTopPadding)
+                * RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE;
     }
 
     @Override
diff --git a/src_no_quickstep/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/src_no_quickstep/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index b193d37..02e0de0 100644
--- a/src_no_quickstep/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/src_no_quickstep/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -18,12 +18,8 @@
 
 import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
 
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.ConstantItem;
 import com.android.launcher3.config.FeatureFlags.BooleanFlag;
 import com.android.launcher3.config.FeatureFlags.FlagState;
-import com.android.launcher3.config.FeatureFlags.IntFlag;
 
 import java.io.PrintWriter;
 
@@ -50,23 +46,6 @@
     }
 
     /**
-     * Creates a new integer flag. Integer flags are always release flags
-     */
-    public static IntFlag getIntFlag(
-            int bugId, String key, int defaultValueInCode, String description) {
-        return new IntFlag(defaultValueInCode);
-    }
-
-    /**
-     * Creates a new debug integer flag and it is saved in LauncherPrefs.
-     */
-    public static IntFlag getIntFlag(
-            int bugId, String key, int defaultValueInCode, String description,
-            @Nullable ConstantItem<Integer> launcherPrefFlag) {
-        return new IntFlag(defaultValueInCode);
-    }
-
-    /**
      * Dumps the current flags state to the print writer
      */
     public static void dump(PrintWriter pw) { }
diff --git a/tests/Android.bp b/tests/Android.bp
index 13a1cbb..3822ff8 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -194,6 +194,7 @@
         "androidx.core_core-animation-testing",
         "androidx.test.ext.junit",
         "androidx.test.rules",
+        "uiautomator-helpers",
         "mockito-robolectric-prebuilt",
         "mockito-kotlin2",
         "platform-parametric-runner-lib",
diff --git a/tests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt b/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
diff --git a/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt b/tests/multivalentTests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
index 95444ba..b5707ff 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestUtil.java
@@ -48,10 +48,8 @@
 
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.config.FeatureFlags.BooleanFlag;
-import com.android.launcher3.config.FeatureFlags.IntFlag;
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.tapl.Workspace;
-import com.android.launcher3.util.rule.TestStabilityRule;
 
 import org.junit.Assert;
 
@@ -67,7 +65,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Predicate;
-import java.util.function.ToIntFunction;
 
 public class TestUtil {
     private static final String TAG = "TestUtil";
@@ -138,20 +135,13 @@
      */
     public static Point[] getCornersAndCenterPositions(LauncherInstrumentation launcher) {
         final Point dimensions = launcher.getWorkspace().getIconGridDimensions();
-        if (TestStabilityRule.isPresubmit()) {
-            // Return only center in presubmit to fit under the presubmit SLO.
-            return new Point[]{
-                    new Point(dimensions.x / 2, dimensions.y / 2)
-            };
-        } else {
-            return new Point[]{
-                    new Point(0, 1),
-                    new Point(0, dimensions.y - 2),
-                    new Point(dimensions.x - 1, 1),
-                    new Point(dimensions.x - 1, dimensions.y - 2),
-                    new Point(dimensions.x / 2, dimensions.y / 2)
-            };
-        }
+        return new Point[]{
+                new Point(0, 1),
+                new Point(0, dimensions.y - 2),
+                new Point(dimensions.x - 1, 1),
+                new Point(dimensions.x - 1, dimensions.y - 2),
+                new Point(dimensions.x / 2, dimensions.y / 2)
+        };
     }
 
     /**
@@ -169,21 +159,6 @@
         };
     }
 
-    /**
-     * Utility class to override a int flag during test. Note that the returned SafeCloseable
-     * must be closed to restore the original state
-     */
-    public static SafeCloseable overrideFlag(IntFlag flag, int value) {
-        ToIntFunction<IntFlag> originalProxy = FeatureFlags.sIntReader;
-        ToIntFunction<IntFlag> testProxy = f -> f == flag ? value : originalProxy.applyAsInt(f);
-        FeatureFlags.sIntReader = testProxy;
-        return () -> {
-            if (FeatureFlags.sIntReader == testProxy) {
-                FeatureFlags.sIntReader = originalProxy;
-            }
-        };
-    }
-
     public static void uninstallDummyApp() throws IOException {
         UiDevice.getInstance(getInstrumentation()).executeShellCommand(
                 "pm uninstall " + DUMMY_PACKAGE);
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS b/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS
new file mode 100644
index 0000000..775b0c7
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/OWNERS
@@ -0,0 +1,18 @@
+set noparent
+
+# Bug component: 1481801
+# People who can approve changes for submission
+#
+
+# Widget Picker OWNERS
+zakcohen@google.com
+shamalip@google.com
+wvk@google.com
+
+# For Tests
+vadimt@google.com
+
+# Launcher OWNERS
+captaincole@google.com
+sunnygoyal@google.com
+
diff --git a/tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
rename to tests/multivalentTests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index ffcf83f..577334b 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -38,17 +38,18 @@
 import com.android.launcher3.util.window.CachedDisplayInfo
 import com.android.launcher3.util.window.WindowManagerProxy
 import com.google.common.truth.Truth
-import org.junit.Rule
-import org.mockito.kotlin.any
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.spy
-import org.mockito.kotlin.whenever
 import java.io.BufferedReader
 import java.io.File
 import java.io.PrintWriter
 import java.io.StringWriter
 import kotlin.math.max
 import kotlin.math.min
+import org.junit.Rule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
 
 /**
  * This is an abstract class for DeviceProfile tests that create an InvariantDeviceProfile based on
@@ -286,6 +287,9 @@
             .thenReturn(
                 if (isGestureMode) NavigationMode.NO_BUTTON else NavigationMode.THREE_BUTTONS
             )
+        doReturn(WindowManagerProxy.INSTANCE[runningContext].isTaskbarDrawnInProcess)
+            .whenever(windowManagerProxy)
+            .isTaskbarDrawnInProcess()
 
         val density = densityDpi / DisplayMetrics.DENSITY_DEFAULT.toFloat()
         val config =
diff --git a/tests/src/com/android/launcher3/AbstractFloatingViewHelperTest.kt b/tests/src/com/android/launcher3/AbstractFloatingViewHelperTest.kt
new file mode 100644
index 0000000..7ff544d
--- /dev/null
+++ b/tests/src/com/android/launcher3/AbstractFloatingViewHelperTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.view.View
+import com.android.launcher3.dragndrop.DragLayer
+import com.android.launcher3.views.ActivityContext
+import org.junit.Before
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
+
+/** Test for AbstractFloatingViewHelper */
+class AbstractFloatingViewHelperTest {
+    private val activityContext: ActivityContext = mock()
+    private val dragLayer: DragLayer = mock()
+    private val view: View = mock()
+    private val folderView: AbstractFloatingView = mock()
+    private val taskMenuView: AbstractFloatingView = mock()
+    private val abstractFloatingViewHelper = AbstractFloatingViewHelper()
+
+    @Before
+    fun setup() {
+        whenever(activityContext.dragLayer).thenReturn(dragLayer)
+        whenever(dragLayer.childCount).thenReturn(3)
+        whenever(dragLayer.getChildAt(0)).thenReturn(view)
+        whenever(dragLayer.getChildAt(1)).thenReturn(folderView)
+        whenever(dragLayer.getChildAt(2)).thenReturn(taskMenuView)
+        whenever(folderView.isOfType(any())).thenAnswer {
+            (it.getArgument<Int>(0) and AbstractFloatingView.TYPE_FOLDER) != 0
+        }
+        whenever(taskMenuView.isOfType(any())).thenAnswer {
+            (it.getArgument<Int>(0) and AbstractFloatingView.TYPE_TASK_MENU) != 0
+        }
+    }
+
+    @Test
+    fun closeOpenViews_all() {
+        abstractFloatingViewHelper.closeOpenViews(
+            activityContext,
+            true,
+            AbstractFloatingView.TYPE_ALL
+        )
+
+        verifyZeroInteractions(view)
+        verify(folderView).close(true)
+        verify(taskMenuView).close(true)
+    }
+
+    @Test
+    fun closeOpenViews_taskMenu() {
+        abstractFloatingViewHelper.closeOpenViews(
+            activityContext,
+            true,
+            AbstractFloatingView.TYPE_TASK_MENU
+        )
+
+        verifyZeroInteractions(view)
+        verify(folderView, never()).close(any())
+        verify(taskMenuView).close(true)
+    }
+
+    @Test
+    fun closeOpenViews_other() {
+        abstractFloatingViewHelper.closeOpenViews(
+            activityContext,
+            true,
+            AbstractFloatingView.TYPE_PIN_IME_POPUP
+        )
+
+        verifyZeroInteractions(view)
+        verify(folderView, never()).close(any())
+        verify(taskMenuView, never()).close(any())
+    }
+
+    @Test
+    fun closeOpenViews_both_animationOff() {
+        abstractFloatingViewHelper.closeOpenViews(
+            activityContext,
+            false,
+            AbstractFloatingView.TYPE_FOLDER or AbstractFloatingView.TYPE_TASK_MENU
+        )
+
+        verifyZeroInteractions(view)
+        verify(folderView).close(false)
+        verify(taskMenuView).close(false)
+    }
+}
diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
index e9d2f6e..2dcbda4 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
+++ b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
@@ -47,8 +47,8 @@
 
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.UserIconInfo;
 import com.android.launcher3.util.rule.TestStabilityRule;
 
@@ -108,7 +108,8 @@
         when(mUserCache.getUserInfo(Process.myUserHandle())).thenReturn(MAIN_ICON_INFO);
         when(mUserCache.getUserInfo(PRIVATE_HANDLE)).thenReturn(PRIVATE_ICON_INFO);
         when(mAllApps.getContext()).thenReturn(mContext);
-        when(mAllApps.getContext().getResources()).thenReturn(mResources);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mContext.getApplicationContext()).thenReturn(getApplicationContext());
         when(mAllApps.getAppsStore()).thenReturn(mAllAppsStore);
         when(mAllApps.getActiveRecyclerView()).thenReturn(mAllAppsRecyclerView);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
@@ -200,14 +201,16 @@
 
     @Test
     public void openPrivateSpaceSettings_triggersCorrectIntent() {
-        Intent expectedIntent = ApiWrapper.getPrivateSpaceSettingsIntent(mContext);
+        Intent expectedIntent = ApiWrapper.INSTANCE.get(mContext).getPrivateSpaceSettingsIntent();
         ArgumentCaptor<Intent> acIntent = ArgumentCaptor.forClass(Intent.class);
         mPrivateProfileManager.setPrivateSpaceSettingsAvailable(true);
 
         mPrivateProfileManager.openPrivateSpaceSettings();
 
         Mockito.verify(mContext).startActivity(acIntent.capture());
-        assertEquals("Intent Action is different", expectedIntent, acIntent.getValue());
+        assertEquals("Intent Action is different",
+                expectedIntent == null ? null : expectedIntent.toUri(0),
+                acIntent.getValue() == null ? null : acIntent.getValue().toUri(0));
     }
 
     private static void awaitTasksCompleted() throws Exception {
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index f01bdb3..fabe3f7 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -5,7 +5,6 @@
 
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.util.rule.FailureWatcher;
-import com.android.launcher3.util.rule.TestStabilityRule;
 
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
@@ -34,11 +33,7 @@
     @Override
     public Statement apply(Statement base, Description description) {
         if (!TestHelpers.isInLauncherProcess()
-                || description.getAnnotation(PortraitLandscape.class) == null
-                // If running in presubmit, don't run in both orientations.
-                // It's important to keep presubmits fast even if we will occasionally miss
-                // regressions in presubmit.
-                || TestStabilityRule.isPresubmit()) {
+                || description.getAnnotation(PortraitLandscape.class) == null) {
             return base;
         }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 847dc4b..245ec09 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -416,6 +416,14 @@
         }
     }
 
+    /** Returns PrivateSpaceContainer if present in view. */
+    @NonNull
+    public PrivateSpaceContainer getPrivateSpaceUnlockedView() {
+        final UiObject2 allAppsContainer = verifyActiveContainer();
+        final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
+        return new PrivateSpaceContainer(mLauncher, appListRecycler);
+    }
+
     protected abstract void verifyVisibleContainerOnDismiss();
 
     /**
diff --git a/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java
new file mode 100644
index 0000000..2c16e01
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 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;
+
+/**
+ * View containing Private Space elements.
+ */
+public class PrivateSpaceContainer {
+    private static final String PS_HEADER_RES_ID = "ps_header_layout";
+    private static final String INSTALL_APP_TITLE = "Install apps";
+    private static final String DIVIDER_RES_ID = "private_space_divider";
+
+    private final LauncherInstrumentation mLauncher;
+    private final UiObject2 mAppListRecycler;
+
+    PrivateSpaceContainer(LauncherInstrumentation launcherInstrumentation,
+            UiObject2 appListRecycler) {
+        mLauncher = launcherInstrumentation;
+        mAppListRecycler = appListRecycler;
+
+        verifyHeaderIsPresent();
+        verifyInstallAppButtonIsPresent();
+        verifyDividerIsPresent();
+    }
+
+    // Assert PS Header is in view.
+    // Assert PS header has the correct elements.
+    private void verifyHeaderIsPresent() {
+        final UiObject2 psHeader = mLauncher.waitForObjectInContainer(mAppListRecycler,
+                PS_HEADER_RES_ID);
+        new PrivateSpaceHeader(mLauncher, psHeader, true);
+    }
+
+
+    // Assert Install App Item is present in view.
+    private void verifyInstallAppButtonIsPresent() {
+        mLauncher.getAllApps().getAppIcon(INSTALL_APP_TITLE);
+    }
+
+    // Assert Sys App Divider is present in view.
+    private void verifyDividerIsPresent() {
+        mLauncher.waitForObjectInContainer(mAppListRecycler, DIVIDER_RES_ID);
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/PrivateSpaceHeader.java b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceHeader.java
new file mode 100644
index 0000000..3a7f038
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceHeader.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 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;
+
+/**
+ * View containing Private Space Header.
+ */
+public class PrivateSpaceHeader {
+    private static final String PS_HEADER_TEXT_RES_ID = "ps_container_header";
+    private static final String SETTINGS_BUTTON_RES_ID = "ps_settings_button";
+    private static final String UNLOCK_BUTTON_VIEW_RES_ID = "ps_lock_unlock_button";
+    private static final String LOCK_ICON_RES_ID = "lock_icon";
+    private static final String LOCK_TEXT_RES_ID = "lock_text";
+
+    private final UiObject2 mPrivateSpaceHeader;
+    private final boolean mPrivateSpaceEnabled;
+    private final LauncherInstrumentation mLauncher;
+
+    PrivateSpaceHeader(LauncherInstrumentation launcherInstrumentation,
+            UiObject2 privateSpaceHeader, boolean privateSpaceEnabled) {
+        mLauncher = launcherInstrumentation;
+        mPrivateSpaceHeader = privateSpaceHeader;
+        mPrivateSpaceEnabled =  privateSpaceEnabled;
+        verifyPrivateSpaceHeaderState();
+    }
+
+    /** Verify elements in Private Space Header as per state */
+    private void verifyPrivateSpaceHeaderState() {
+        if (mPrivateSpaceEnabled) {
+            verifyUnlockedState();
+        } else {
+            mLauncher.fail("Private Space found in non enabled state");
+        }
+    }
+
+    /** Verify Unlocked State elements in Private Space Header */
+    private void verifyUnlockedState() {
+        UiObject2 headerText = mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+                PS_HEADER_TEXT_RES_ID);
+        mLauncher.assertEquals("PS Header Text is incorrect ",
+                "Private", headerText.getText());
+
+        UiObject2 settingsButton = mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+                SETTINGS_BUTTON_RES_ID);
+        mLauncher.waitForObjectEnabled(settingsButton, "Private Space Settings Button");
+        mLauncher.assertTrue("PS Settings button is non-clickable", settingsButton.isClickable());
+
+        UiObject2 unLockButtonView = mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+                UNLOCK_BUTTON_VIEW_RES_ID);
+        mLauncher.waitForObjectEnabled(unLockButtonView, "Private Space Unlock Button");
+        mLauncher.assertTrue("PS Unlock Button is non-clickable", unLockButtonView.isClickable());
+
+        mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+                LOCK_ICON_RES_ID);
+
+        UiObject2 lockText = mLauncher.waitForObjectInContainer(mPrivateSpaceHeader,
+                LOCK_TEXT_RES_ID);
+        mLauncher.assertEquals("PS lock text is incorrect", "Lock", lockText.getText());
+
+    }
+}