Merge "Adding support for private/clone profile badge in Launcher3." into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 874f862..ace2210 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -32,7 +32,7 @@
     name: "enable_responsive_workspace"
     namespace: "launcher"
     description: "Enables new workspace grid calculations method."
-    bug: "241386436"
+    bug: "302189128"
 }
 
 flag {
@@ -41,3 +41,17 @@
     description: "Enable updated overview icon and menu within task."
     bug: "257950105"
 }
+
+flag {
+    name: "enable_taskbar_no_recreate"
+    namespace: "launcher"
+    description: "Enables taskbar with no recreation from lifecycle changes of TaskbarActivityContext."
+    bug: "299193589"
+}
+
+flag {
+    name: "enable_home_transition_listener"
+    namespace: "launcher"
+    description: "Enables launcher to listen to all transitions that include home activity"
+    bug: "306053414"
+}
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index f8b08f8..dde69e3 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -135,7 +135,7 @@
   }
 }
 
-// Next value 52
+// Next value 53
 enum Attribute {
   option allow_alias = true;
 
@@ -188,6 +188,7 @@
   ALL_APPS_SEARCH_RESULT_EDUCARD = 43;
   ALL_APPS_SEARCH_RESULT_LOCATION = 50;
   ALL_APPS_SEARCH_RESULT_TEXT_HEADER = 51;
+  ALL_APPS_SEARCH_RESULT_NO_FULFILLMENT = 52;
 
   // Result sources
   DATA_SOURCE_APPSEARCH_APP_PREVIEW = 45;
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 6d958ed..82f8ebe 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -30,7 +30,9 @@
     <uses-permission android:name="android.permission.REMOVE_TASKS"/>
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
     <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
+    <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
     <uses-permission android:name="android.permission.STATUS_BAR"/>
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE"/>
     <uses-permission android:name="android.permission.STOP_APP_SWITCHES"/>
     <uses-permission android:name="android.permission.SET_ORIENTATION"/>
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER"/>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index c5d0b95..e77d2c6 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1057,9 +1057,9 @@
         boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW
                 && BlurUtils.supportsBlursOnWindows();
 
-        ObjectAnimator backgroundRadiusAnim =
-                ObjectAnimator.ofFloat(mLauncher.getDepthController().stateDepth,
-                                MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mLauncher))
+        LaunchDepthController depthController = new LaunchDepthController(mLauncher);
+        ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController.stateDepth,
+                        MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mLauncher))
                 .setDuration(APP_LAUNCH_DURATION);
 
         if (allowBlurringLauncher) {
@@ -1085,6 +1085,9 @@
                     new SurfaceControl.Transaction().remove(dimLayer).apply()));
         }
 
+        backgroundRadiusAnim.addListener(
+                AnimatorListeners.forEndCallback(depthController::dispose));
+
         return backgroundRadiusAnim;
     }
 
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 957db64..882682d 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -94,6 +94,18 @@
         }
     }
 
+    /**
+     * Cleans up after this controller so it can be garbage collected without leaving traces.
+     */
+    public void dispose() {
+        removeSecondaryListeners();
+
+        if (mLauncher.getRootView() != null && mOnAttachListener != null) {
+            mLauncher.getRootView().removeOnAttachStateChangeListener(mOnAttachListener);
+            mOnAttachListener = null;
+        }
+    }
+
     private void removeSecondaryListeners() {
         if (mCrossWindowBlurListener != null) {
             CrossWindowBlurListeners.getInstance().removeListener(mCrossWindowBlurListener);
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index 00a282a..a7e8118 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
 import android.os.Debug;
 import android.os.SystemProperties;
@@ -106,13 +107,6 @@
     }
 
     /**
-     * Whether desktop mode is supported.
-     */
-    private boolean isDesktopModeSupported() {
-        return SystemProperties.getBoolean("persist.wm.debug.desktop_mode_2", false);
-    }
-
-    /**
      * Whether freeform windows are visible in desktop mode.
      */
     public boolean areFreeformTasksVisible() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index dda8446..f58fd45 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
+
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 
@@ -28,7 +30,6 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
@@ -109,7 +110,7 @@
         DesktopVisibilityController desktopController =
                 LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
         final boolean onDesktop =
-                DesktopTaskView.DESKTOP_MODE_SUPPORTED
+                isDesktopModeSupported()
                         && desktopController != null
                         && desktopController.areFreeformTasksVisible();
 
@@ -136,7 +137,7 @@
 
         // Hide all desktop tasks and show them on the hidden tile
         int hiddenDesktopTasks = 0;
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             DesktopTask desktopTask = findDesktopTask(tasks);
             if (desktopTask != null) {
                 hiddenDesktopTasks = desktopTask.tasks.size();
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 445a1bd..09376d7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_HOME_TRANSITION_LISTENER;
 import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
 import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
 import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
@@ -39,6 +38,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
@@ -101,7 +101,7 @@
 
         mLauncher.setTaskbarUIController(this);
 
-        if (!ENABLE_HOME_TRANSITION_LISTENER.get()) {
+        if (!FeatureFlags.enableHomeTransitionListener()) {
             onLauncherVisibilityChanged(mLauncher.hasBeenResumed(), true /* fromInit */);
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index d4d6b26..6ee151b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -20,6 +20,7 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
 
@@ -29,8 +30,8 @@
 import static com.android.launcher3.Flags.enableCursorHoverStates;
 import static com.android.launcher3.Utilities.calculateTextHeight;
 import static com.android.launcher3.Utilities.isRunningInTestHarness;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NO_RECREATION;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
+import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN;
@@ -128,6 +129,7 @@
 import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.Optional;
+import java.util.function.Consumer;
 
 /**
  * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
@@ -173,13 +175,16 @@
 
     private final TaskbarShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
 
+    private DeviceProfile mTransientTaskbarDeviceProfile;
+
+    private DeviceProfile mPersistentTaskbarDeviceProfile;
+
     public TaskbarActivityContext(Context windowContext, DeviceProfile launcherDp,
             TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
             unfoldTransitionProgressProvider) {
         super(windowContext);
 
         applyDeviceProfile(launcherDp);
-
         final Resources resources = getResources();
 
         mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
@@ -254,9 +259,9 @@
                 new TaskbarViewController(this, taskbarView),
                 new TaskbarScrimViewController(this, taskbarScrimView),
                 new TaskbarUnfoldAnimationController(this, unfoldTransitionProgressProvider,
-                    mWindowManager,
-                    new RotationChangeProvider(c.getSystemService(DisplayManager.class), this,
-                        getMainThreadHandler())),
+                        mWindowManager,
+                        new RotationChangeProvider(c.getSystemService(DisplayManager.class), this,
+                                getMainThreadHandler())),
                 new TaskbarKeyguardController(this),
                 new StashedHandleViewController(this, stashedHandleView),
                 new TaskbarStashController(this),
@@ -274,7 +279,7 @@
                         : TaskbarRecentAppsController.DEFAULT,
                 new TaskbarEduTooltipController(this),
                 new KeyboardQuickSwitchController(),
-                new TaskbarDividerPopupController(this),
+                new TaskbarPinningController(this),
                 bubbleControllersOptional);
     }
 
@@ -295,17 +300,34 @@
      * the icon size
      */
     private void applyDeviceProfile(DeviceProfile originDeviceProfile) {
-        mDeviceProfile = originDeviceProfile.toBuilder(this)
-                .withDimensionsOverride(deviceProfile -> {
-                    // Taskbar should match the number of icons of hotseat
-                    deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons;
-                    // Same QSB width to have a smooth animation
-                    deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth;
+        Consumer<DeviceProfile> overrideProvider = deviceProfile -> {
+            // Taskbar should match the number of icons of hotseat
+            deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons;
+            // Same QSB width to have a smooth animation
+            deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth;
 
-                    // Update icon size
-                    deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
-                    deviceProfile.updateIconSize(1f, getResources());
-                }).build();
+            // Update icon size
+            deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
+            deviceProfile.updateIconSize(1f, getResources());
+        };
+        mDeviceProfile = originDeviceProfile.toBuilder(this)
+                .withDimensionsOverride(overrideProvider).build();
+
+        if (DisplayController.isTransientTaskbar(this)) {
+            mTransientTaskbarDeviceProfile = mDeviceProfile;
+            mPersistentTaskbarDeviceProfile = mDeviceProfile
+                    .toBuilder(this)
+                    .withDimensionsOverride(overrideProvider)
+                    .setIsTransientTaskbar(false)
+                    .build();
+        } else {
+            mPersistentTaskbarDeviceProfile = mDeviceProfile;
+            mTransientTaskbarDeviceProfile = mDeviceProfile
+                    .toBuilder(this)
+                    .withDimensionsOverride(overrideProvider)
+                    .setIsTransientTaskbar(true)
+                    .build();
+        }
         mNavMode = DisplayController.getNavigationMode(this);
     }
 
@@ -339,7 +361,7 @@
             mIsDestroyed = false;
         }
 
-        if (!ENABLE_TASKBAR_NO_RECREATION.get() && !mAddedWindow) {
+        if (!enableTaskbarNoRecreate() && !mAddedWindow) {
             mWindowManager.addView(mDragLayer, mWindowLayoutParams);
             mAddedWindow = true;
         } else {
@@ -391,7 +413,8 @@
 
     /**
      * Creates LayoutParams for adding a view directly to WindowManager as a new window.
-     * @param type The window type to pass to the created WindowManager.LayoutParams.
+     *
+     * @param type  The window type to pass to the created WindowManager.LayoutParams.
      * @param title The window title to pass to the created WindowManager.LayoutParams.
      */
     public WindowManager.LayoutParams createDefaultWindowLayoutParams(int type, String title) {
@@ -430,9 +453,10 @@
      * for taskbar showing as navigation bar
      */
     private WindowManager.LayoutParams createAllWindowParams() {
+        final int windowType =
+                FLAG_HIDE_NAVBAR_WINDOW ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL;
         WindowManager.LayoutParams windowLayoutParams =
-                createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL,
-                        TaskbarActivityContext.WINDOW_TITLE);
+                createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE);
         boolean isPhoneNavMode = TaskbarManager.isPhoneButtonNavMode(this);
         if (!isPhoneNavMode) {
             return windowLayoutParams;
@@ -445,7 +469,7 @@
         windowLayoutParams.paramsForRotation = new WindowManager.LayoutParams[4];
         for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
             WindowManager.LayoutParams lp =
-                    createDefaultWindowLayoutParams(TYPE_NAVIGATION_BAR_PANEL,
+                    createDefaultWindowLayoutParams(windowType,
                             TaskbarActivityContext.WINDOW_TITLE);
             switch (rot) {
                 case Surface.ROTATION_0, Surface.ROTATION_180 -> {
@@ -695,7 +719,7 @@
         mIsDestroyed = true;
         setUIController(TaskbarUIController.DEFAULT);
         mControllers.onDestroy();
-        if (!ENABLE_TASKBAR_NO_RECREATION.get() && !FLAG_HIDE_NAVBAR_WINDOW) {
+        if (!enableTaskbarNoRecreate() && !FLAG_HIDE_NAVBAR_WINDOW) {
             mWindowManager.removeViewImmediate(mDragLayer);
             mAddedWindow = false;
         }
@@ -794,7 +818,7 @@
         // Overlay AFVs are in a separate window and do not require Taskbar to be fullscreen.
         if (!isDragInProgress
                 && !AbstractFloatingView.hasOpenView(
-                        this, TYPE_ALL & ~TYPE_TASKBAR_OVERLAY_PROXY)) {
+                this, TYPE_ALL & ~TYPE_TASKBAR_OVERLAY_PROXY)) {
             // Reverts Taskbar window to its original size
             setTaskbarWindowFullscreen(false);
         }
@@ -853,7 +877,7 @@
                 ? resources.getDimensionPixelSize(R.dimen.arrow_toast_arrow_height)
                 + (resources.getDimensionPixelSize(R.dimen.taskbar_tooltip_vertical_padding) * 2)
                 + calculateTextHeight(
-                        resources.getDimensionPixelSize(R.dimen.arrow_toast_text_size))
+                resources.getDimensionPixelSize(R.dimen.arrow_toast_text_size))
                 : 0;
 
         // Return transient taskbar window height when pinning feature is enabled, so taskbar view
@@ -865,7 +889,7 @@
             return transientTaskbarDp.taskbarHeight
                     + (2 * transientTaskbarDp.taskbarBottomMargin)
                     + Math.max(extraHeightForTaskbarTooltips, resources.getDimensionPixelSize(
-                            R.dimen.transient_taskbar_shadow_blur));
+                    R.dimen.transient_taskbar_shadow_blur));
         }
 
 
@@ -878,6 +902,14 @@
         return getResources().getDimensionPixelSize(R.dimen.taskbar_suw_frame);
     }
 
+    public DeviceProfile getTransientTaskbarDeviceProfile() {
+        return mTransientTaskbarDeviceProfile;
+    }
+
+    public DeviceProfile getPersistentTaskbarDeviceProfile() {
+        return mPersistentTaskbarDeviceProfile;
+    }
+
     /**
      * Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
      * window.
@@ -1290,7 +1322,7 @@
 
     void notifyUpdateLayoutParams() {
         if (mDragLayer.isAttachedToWindow()) {
-            if (ENABLE_TASKBAR_NO_RECREATION.get()) {
+            if (enableTaskbarNoRecreate()) {
                 mWindowManager.updateViewLayout(mDragLayer.getRootView(), mWindowLayoutParams);
             } else {
                 mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index d237c1f..d6016f1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -29,26 +29,39 @@
 import com.android.launcher3.Utilities.mapRange
 import com.android.launcher3.Utilities.mapToRange
 import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
+import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_PERSISTENT
+import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_TRANSIENT
 import com.android.launcher3.util.DisplayController
+import kotlin.math.min
 
 /** Helps draw the taskbar background, made up of a rectangle plus two inverted rounded corners. */
-class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
+class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
 
     private val isInSetup: Boolean = !context.isUserSetupComplete
     private val DARK_THEME_SHADOW_ALPHA = 51f
     private val LIGHT_THEME_SHADOW_ALPHA = 25f
 
+    private val maxTransientTaskbarHeight =
+        context.transientTaskbarDeviceProfile.taskbarHeight.toFloat()
+    private val maxPersistentTaskbarHeight =
+        context.persistentTaskbarDeviceProfile.taskbarHeight.toFloat()
+    var backgroundProgress =
+        if (DisplayController.isTransientTaskbar(context)) {
+            PINNING_TRANSIENT
+        } else {
+            PINNING_PERSISTENT
+        }
+
+    var isAnimatingPinning = false
+
     val paint = Paint()
     val lastDrawnTransientRect = RectF()
     var backgroundHeight = context.deviceProfile.taskbarHeight.toFloat()
     var translationYForSwipe = 0f
     var translationYForStash = 0f
 
-    private var maxBackgroundHeight = context.deviceProfile.taskbarHeight.toFloat()
     private val transientBackgroundBounds = context.transientTaskbarBounds
 
-    private val isTransientTaskbar = DisplayController.isTransientTaskbar(context)
-
     private val shadowAlpha: Float
     private var shadowBlur = 0f
     private var keyShadowDistance = 0f
@@ -75,13 +88,6 @@
         paint.flags = Paint.ANTI_ALIAS_FLAG
         paint.style = Paint.Style.FILL
 
-        if (isTransientTaskbar) {
-            val res = context.resources
-            bottomMargin = res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin)
-            shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
-            keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
-        }
-
         shadowAlpha =
             if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA
             else LIGHT_THEME_SHADOW_ALPHA
@@ -90,10 +96,11 @@
     }
 
     fun updateStashedHandleWidth(dp: DeviceProfile, res: Resources) {
-        stashedHandleWidth = res.getDimensionPixelSize(
+        stashedHandleWidth =
+            res.getDimensionPixelSize(
                 if (TaskbarManager.isPhoneMode(dp)) R.dimen.taskbar_stashed_small_screen
                 else R.dimen.taskbar_stashed_handle_width
-        )
+            )
     }
 
     /**
@@ -102,7 +109,7 @@
      * @param cornerRoundness 0 has no round corner, 1 has complete round corner.
      */
     fun setCornerRoundness(cornerRoundness: Float) {
-        if (isTransientTaskbar && !transientBackgroundBounds.isEmpty) {
+        if (DisplayController.isTransientTaskbar(context) && !transientBackgroundBounds.isEmpty) {
             return
         }
 
@@ -126,63 +133,123 @@
 
     /** Draws the background with the given paint and height, on the provided canvas. */
     fun draw(canvas: Canvas) {
+        if (isInSetup) return
+        val isTransientTaskbar = backgroundProgress == 0f
         canvas.save()
-        if (!isTransientTaskbar || transientBackgroundBounds.isEmpty) {
-            canvas.translate(0f, canvas.height - backgroundHeight - bottomMargin)
-            // Draw the background behind taskbar content.
-            canvas.drawRect(0f, 0f, canvas.width.toFloat(), backgroundHeight, paint)
-
-            // Draw the inverted rounded corners above the taskbar.
-            canvas.translate(0f, -leftCornerRadius)
-            canvas.drawPath(invertedLeftCornerPath, paint)
-            canvas.translate(0f, leftCornerRadius)
-            canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
-            canvas.drawPath(invertedRightCornerPath, paint)
-        } else if (!isInSetup) {
-            // backgroundHeight is a value from [0...maxBackgroundHeight], so we can use it as a
-            // proxy to figure out the animation progress of the stash/unstash animation.
-            val progress = backgroundHeight / maxBackgroundHeight
-
-            // At progress 0, we draw the background as the stashed handle.
-            // At progress 1, we draw the background as the full taskbar.
-            val newBackgroundHeight =
-                mapRange(progress, stashedHandleHeight.toFloat(), maxBackgroundHeight)
-            val fullWidth = transientBackgroundBounds.width()
-            val newWidth = mapRange(progress, stashedHandleWidth.toFloat(), fullWidth.toFloat())
-            val halfWidthDelta = (fullWidth - newWidth) / 2f
-            val radius = newBackgroundHeight / 2f
-            val bottomMarginProgress = bottomMargin * ((1f - progress) / 2f)
-
-            // Aligns the bottom with the bottom of the stashed handle.
-            val bottom =
-                canvas.height - bottomMargin +
-                    bottomMarginProgress +
-                    translationYForSwipe +
-                    translationYForStash +
-                    -mapRange(1f - progress, 0f, stashedHandleHeight / 2f)
-
-            // Draw shadow.
-            val newShadowAlpha =
-                mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
-            paint.setShadowLayer(
-                shadowBlur,
-                0f,
-                keyShadowDistance,
-                setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
-            )
-
-            lastDrawnTransientRect.set(
-                transientBackgroundBounds.left + halfWidthDelta,
-                bottom - newBackgroundHeight,
-                transientBackgroundBounds.right - halfWidthDelta,
-                bottom
-            )
-            val horizontalInset = fullWidth * widthInsetPercentage
-            lastDrawnTransientRect.inset(horizontalInset, 0f)
-
-            canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, paint)
+        if (!isTransientTaskbar || transientBackgroundBounds.isEmpty || isAnimatingPinning) {
+            drawPersistentBackground(canvas)
         }
         canvas.restore()
+        canvas.save()
+        if (isAnimatingPinning || isTransientTaskbar) {
+            drawTransientBackground(canvas)
+        }
+        canvas.restore()
+    }
+
+    private fun drawPersistentBackground(canvas: Canvas) {
+        if (isAnimatingPinning) {
+            val persistentTaskbarHeight = maxPersistentTaskbarHeight * backgroundProgress
+            canvas.translate(0f, canvas.height - persistentTaskbarHeight)
+            // Draw the background behind taskbar content.
+            canvas.drawRect(0f, 0f, canvas.width.toFloat(), persistentTaskbarHeight, paint)
+        } else {
+            val persistentTaskbarHeight = min(maxPersistentTaskbarHeight, backgroundHeight)
+            canvas.translate(0f, canvas.height - persistentTaskbarHeight)
+            // Draw the background behind taskbar content.
+            canvas.drawRect(0f, 0f, canvas.width.toFloat(), persistentTaskbarHeight, paint)
+        }
+
+        // Draw the inverted rounded corners above the taskbar.
+        canvas.translate(0f, -leftCornerRadius)
+        canvas.drawPath(invertedLeftCornerPath, paint)
+        canvas.translate(0f, leftCornerRadius)
+        canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
+        canvas.drawPath(invertedRightCornerPath, paint)
+    }
+
+    private fun drawTransientBackground(canvas: Canvas) {
+        val res = context.resources
+        val transientTaskbarHeight = maxTransientTaskbarHeight * (1f - backgroundProgress)
+        val heightProgressWhileAnimating =
+            if (isAnimatingPinning) transientTaskbarHeight else backgroundHeight
+
+        var progress = heightProgressWhileAnimating / maxTransientTaskbarHeight
+        progress = Math.round(progress * 100f) / 100f
+        if (isAnimatingPinning) {
+            var scale = transientTaskbarHeight / maxTransientTaskbarHeight
+            scale = Math.round(scale * 100f) / 100f
+            bottomMargin =
+                mapRange(
+                        scale,
+                        0f,
+                        res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin).toFloat()
+                    )
+                    .toInt()
+            shadowBlur =
+                mapRange(scale, 0f, res.getDimension(R.dimen.transient_taskbar_shadow_blur))
+            keyShadowDistance =
+                mapRange(scale, 0f, res.getDimension(R.dimen.transient_taskbar_key_shadow_distance))
+        } else {
+            bottomMargin = res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin)
+            shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
+            keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
+        }
+
+        // At progress 0, we draw the background as the stashed handle.
+        // At progress 1, we draw the background as the full taskbar.
+        // Min height capped to max persistent taskbar height for animation
+        val backgroundHeightWhileAnimating =
+            if (isAnimatingPinning) maxPersistentTaskbarHeight else stashedHandleHeight.toFloat()
+        val newBackgroundHeight =
+            mapRange(progress, backgroundHeightWhileAnimating, maxTransientTaskbarHeight)
+        val fullWidth = transientBackgroundBounds.width()
+
+        // .9f is here to restrict min width of the background while animating, so transient
+        // background keeps it pill shape until animation end.
+        val animationWidth =
+            if (DisplayController.isTransientTaskbar(context)) fullWidth.toFloat() * .9f
+            else fullWidth.toFloat()
+        val backgroundWidthWhileAnimating =
+            if (isAnimatingPinning) animationWidth else stashedHandleWidth.toFloat()
+
+        val newWidth = mapRange(progress, backgroundWidthWhileAnimating, fullWidth.toFloat())
+        val halfWidthDelta = (fullWidth - newWidth) / 2f
+        val radius = newBackgroundHeight / 2f
+        val bottomMarginProgress = bottomMargin * ((1f - progress) / 2f)
+
+        // Aligns the bottom with the bottom of the stashed handle.
+        val bottom =
+            canvas.height - bottomMargin +
+                bottomMarginProgress +
+                translationYForSwipe +
+                translationYForStash +
+                -mapRange(
+                    1f - progress,
+                    0f,
+                    if (isAnimatingPinning) 0f else stashedHandleHeight / 2f
+                )
+
+        // Draw shadow.
+        val newShadowAlpha =
+            mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
+        paint.setShadowLayer(
+            shadowBlur,
+            0f,
+            keyShadowDistance,
+            setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
+        )
+
+        lastDrawnTransientRect.set(
+            transientBackgroundBounds.left + halfWidthDelta,
+            bottom - newBackgroundHeight,
+            transientBackgroundBounds.right - halfWidthDelta,
+            bottom
+        )
+        val horizontalInset = fullWidth * widthInsetPercentage
+        lastDrawnTransientRect.inset(horizontalInset, 0f)
+
+        canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, paint)
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index d82f501..f9ddc3d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -62,7 +62,7 @@
     public final TaskbarOverlayController taskbarOverlayController;
     public final TaskbarEduTooltipController taskbarEduTooltipController;
     public final KeyboardQuickSwitchController keyboardQuickSwitchController;
-    public final TaskbarDividerPopupController taskbarPinningController;
+    public final TaskbarPinningController taskbarPinningController;
     public final Optional<BubbleControllers> bubbleControllers;
 
     @Nullable private LoggableTaskbarController[] mControllersToLog = null;
@@ -110,7 +110,7 @@
             TaskbarRecentAppsController taskbarRecentAppsController,
             TaskbarEduTooltipController taskbarEduTooltipController,
             KeyboardQuickSwitchController keyboardQuickSwitchController,
-            TaskbarDividerPopupController taskbarPinningController,
+            TaskbarPinningController taskbarPinningController,
             Optional<BubbleControllers> bubbleControllers) {
         this.taskbarActivityContext = taskbarActivityContext;
         this.taskbarDragController = taskbarDragController;
@@ -171,7 +171,7 @@
         taskbarTranslationController.init(this);
         taskbarEduTooltipController.init(this);
         keyboardQuickSwitchController.init(this);
-        taskbarPinningController.init(this);
+        taskbarPinningController.init(this, mSharedState);
         bubbleControllers.ifPresent(controllers -> controllers.init(this));
 
         mControllersToLog = new LoggableTaskbarController[] {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupController.kt
deleted file mode 100644
index a2c61ce..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupController.kt
+++ /dev/null
@@ -1,82 +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.taskbar
-
-import android.view.View
-import com.android.launcher3.LauncherPrefs
-import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
-import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE
-import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN
-import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_PINNED
-import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_UNPINNED
-import com.android.launcher3.taskbar.TaskbarDividerPopupView.Companion.createAndPopulate
-import java.io.PrintWriter
-
-/** Controls taskbar pinning through a popup view. */
-class TaskbarDividerPopupController(private val context: TaskbarActivityContext) :
-    TaskbarControllers.LoggableTaskbarController {
-
-    private lateinit var controllers: TaskbarControllers
-    private val launcherPrefs = LauncherPrefs.get(context)
-    private val statsLogManager = context.statsLogManager
-
-    fun init(taskbarControllers: TaskbarControllers) {
-        controllers = taskbarControllers
-    }
-
-    fun showPinningView(view: View) {
-        context.isTaskbarWindowFullscreen = true
-
-        view.post {
-            val popupView = createAndPopulate(view, context)
-            popupView.requestFocus()
-
-            popupView.onCloseCallback =
-                callback@{ didPreferenceChange ->
-                    statsLogManager.logger().log(LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE)
-                    context.dragLayer.post { context.onPopupVisibilityChanged(false) }
-
-                    if (!didPreferenceChange) {
-                        return@callback
-                    }
-
-                    if (launcherPrefs.get(TASKBAR_PINNING)) {
-                        animateTransientToPersistentTaskbar()
-                        statsLogManager.logger().log(LAUNCHER_TASKBAR_PINNED)
-                    } else {
-                        animatePersistentToTransientTaskbar()
-                        statsLogManager.logger().log(LAUNCHER_TASKBAR_UNPINNED)
-                    }
-                }
-            popupView.changePreference = {
-                launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
-            }
-            context.onPopupVisibilityChanged(true)
-            popupView.show()
-            statsLogManager.logger().log(LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN)
-        }
-    }
-
-    // TODO(b/265436799): provide animation/transition from transient taskbar to persistent one
-    private fun animateTransientToPersistentTaskbar() {}
-
-    // TODO(b/265436799): provide animation/transition from persistent taskbar to transient one
-    private fun animatePersistentToTransientTaskbar() {}
-
-    override fun dumpLogs(prefix: String, pw: PrintWriter) {
-        pw.println(prefix + "TaskbarPinningController:")
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
index b200858..d13b53f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
@@ -75,12 +75,6 @@
     /** Callback invoked when the pinning popup view is closing. */
     var onCloseCallback: (preferenceChanged: Boolean) -> Unit = {}
 
-    /**
-     * Callback invoked when the user preference changes in popup view. Preference change will be
-     * based upon current value stored in [LauncherPrefs] for `TASKBAR_PINNING`
-     */
-    var changePreference: () -> Unit = {}
-
     init {
         // This synchronizes the arrow and menu to open at the same time
         mOpenChildFadeStartDelay = mOpenFadeStartDelay
@@ -185,8 +179,6 @@
 
     private fun onClickAlwaysShowTaskbarSwitchOption() {
         didPreferenceChange = true
-        changePreference()
-        changePreference = {}
         // Allow switch animation to finish and then close the popup.
         postDelayed(DIVIDER_POPUP_CLOSING_DELAY) {
             if (isOpen) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index e521154..1eb8d53 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -73,6 +73,8 @@
     private SafeCloseable mViewCaptureCloseable;
 
     private float mTaskbarBackgroundOffset;
+    private float mTaskbarBackgroundProgress;
+    private boolean mIsAnimatingTaskbarPinning = false;
 
     private final MultiPropertyFactory<TaskbarDragLayer> mTaskbarBackgroundAlpha;
 
@@ -162,10 +164,19 @@
         float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
                 * (1f - mTaskbarBackgroundOffset);
         mBackgroundRenderer.setBackgroundHeight(backgroundHeight);
+        mBackgroundRenderer.setBackgroundProgress(mTaskbarBackgroundProgress);
         mBackgroundRenderer.draw(canvas);
         super.dispatchDraw(canvas);
     }
 
+    /**
+     * Sets animation boolean when taskbar pinning animation starts or stops.
+     */
+    public void setAnimatingTaskbarPinning(boolean animatingTaskbarPinning) {
+        mIsAnimatingTaskbarPinning = animatingTaskbarPinning;
+        mBackgroundRenderer.setAnimatingPinning(mIsAnimatingTaskbarPinning);
+    }
+
     protected MultiProperty getBackgroundRendererAlpha() {
         return mTaskbarBackgroundAlpha.get(INDEX_ALL_OTHER_STATES);
     }
@@ -175,6 +186,15 @@
     }
 
     /**
+     * Sets the value for taskbar background switching between persistent and transient backgrounds.
+     * @param progress 0 is transient background, 1 is persistent background.
+     */
+    protected void setTaskbarBackgroundProgress(float progress) {
+        mTaskbarBackgroundProgress = progress;
+        invalidate();
+    }
+
+    /**
      * Sets the translation of the background color behind all the Taskbar contents.
      * @param offset 0 is fully onscreen, 1 is fully offscreen.
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 867b062..73e32ab 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
+import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
+
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -24,6 +27,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.util.DimensionUtils;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.launcher3.util.TouchController;
 
@@ -58,6 +62,9 @@
     // changes the inset visibility.
     private final AnimatedFloat mTaskbarAlpha = new AnimatedFloat(this::updateTaskbarAlpha);
 
+    private final AnimatedFloat mTaskbarBackgroundProgress = new AnimatedFloat(
+            this::updateTaskbarBackgroundProgress);
+
     // Initialized in init.
     private TaskbarControllers mControllers;
     private TaskbarStashViaTouchController mTaskbarStashViaTouchController;
@@ -83,6 +90,10 @@
         mOnBackgroundNavButtonColorIntensity = mControllers.navbarButtonsViewController
                 .getOnTaskbarBackgroundNavButtonColorOverride();
 
+        mTaskbarBackgroundProgress.updateValue(DisplayController.isTransientTaskbar(mActivity)
+                ? PINNING_TRANSIENT
+                : PINNING_PERSISTENT);
+
         mBgTaskbar.value = 1;
         mKeyguardBgTaskbar.value = 1;
         mNotificationShadeBgTaskbar.value = 1;
@@ -138,6 +149,11 @@
         return mBgOffset;
     }
 
+    // AnimatedFloat is for animating between pinned and transient taskbar
+    public AnimatedFloat getTaskbarBackgroundProgress() {
+        return mTaskbarBackgroundProgress;
+    }
+
     public AnimatedFloat getTaskbarAlpha() {
         return mTaskbarAlpha;
     }
@@ -180,10 +196,13 @@
 
     private void updateBackgroundOffset() {
         mTaskbarDragLayer.setTaskbarBackgroundOffset(mBgOffset.value);
-
         updateOnBackgroundNavButtonColorIntensity();
     }
 
+    private void updateTaskbarBackgroundProgress() {
+        mTaskbarDragLayer.setTaskbarBackgroundProgress(mTaskbarBackgroundProgress.value);
+    }
+
     private void updateTaskbarAlpha() {
         mTaskbarDragLayer.setAlpha(mTaskbarAlpha.value);
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 56ba460..267b15c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -206,12 +206,14 @@
                     updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false);
                     // TODO(b/279514548) Cleans up bad state that can occur when user interacts with
                     // taskbar on top of transparent activity.
-                    if (finalState == LauncherState.NORMAL && mLauncher.hasBeenResumed()) {
+                    if (!FeatureFlags.enableHomeTransitionListener()
+                            && finalState == LauncherState.NORMAL
+                            && mLauncher.hasBeenResumed()) {
                         updateStateForFlag(FLAG_VISIBLE, true);
                     }
                     applyState();
                     boolean disallowLongClick =
-                            FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
+                            FeatureFlags.enableSplitContextually()
                                     ? mLauncher.isSplitSelectionEnabled()
                                     : finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
                     com.android.launcher3.taskbar.Utilities.setOverviewDragState(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 6dfd243..9bb7e67 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -18,12 +18,12 @@
 import static android.content.Context.RECEIVER_NOT_EXPORTED;
 import static android.content.pm.PackageManager.FEATURE_PC;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 
-import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
-import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
+import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NO_RECREATION;
+import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
 import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
@@ -31,13 +31,11 @@
 import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
 
 import android.annotation.SuppressLint;
-import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
@@ -59,12 +57,11 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.quickstep.RecentsActivity;
@@ -147,34 +144,25 @@
     private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver =
             new SimpleBroadcastReceiver(this::showTaskbarFromBroadcast);
 
-    private final SharedPreferences.OnSharedPreferenceChangeListener
-            mTaskbarPinningPreferenceChangeListener = (sharedPreferences, key) -> {
-                if (TASKBAR_PINNING_KEY.equals(key)) {
-                    recreateTaskbar();
-                }
-            };
-
-    private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
-            new ActivityLifecycleCallbacksAdapter() {
-                @Override
-                public void onActivityDestroyed(Activity activity) {
-                    if (mActivity != activity) return;
-                    if (mActivity != null) {
-                        mActivity.removeOnDeviceProfileChangeListener(
-                                mDebugActivityDeviceProfileChanged);
-                        Log.d(TASKBAR_NOT_DESTROYED_TAG,
-                                "unregistering activity lifecycle callbacks from "
-                                        + "onActivityDestroyed.");
-                        mActivity.unregisterActivityLifecycleCallbacks(this);
-                    }
-                    mActivity = null;
-                    debugWhyTaskbarNotDestroyed("clearActivity");
-                    if (mTaskbarActivityContext != null) {
-                        mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
-                    }
-                    mUnfoldProgressProvider.setSourceProvider(null);
-                }
-            };
+    private final Runnable mActivityOnDestroyCallback = new Runnable() {
+        @Override
+        public void run() {
+            if (mActivity != null) {
+                mActivity.removeOnDeviceProfileChangeListener(
+                        mDebugActivityDeviceProfileChanged);
+                Log.d(TASKBAR_NOT_DESTROYED_TAG,
+                        "unregistering activity lifecycle callbacks from "
+                                + "onActivityDestroyed.");
+                mActivity.removeEventCallback(EVENT_DESTROYED, this);
+            }
+            mActivity = null;
+            debugWhyTaskbarNotDestroyed("clearActivity");
+            if (mTaskbarActivityContext != null) {
+                mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
+            }
+            mUnfoldProgressProvider.setSourceProvider(null);
+        }
+    };
 
     UnfoldTransitionProgressProvider.TransitionProgressListener mUnfoldTransitionProgressListener =
             new UnfoldTransitionProgressProvider.TransitionProgressListener() {
@@ -209,8 +197,9 @@
     public TaskbarManager(TouchInteractionService service) {
         Display display =
                 service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
-        mContext = service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null);
-        if (ENABLE_TASKBAR_NO_RECREATION.get()) {
+        mContext = service.createWindowContext(display,
+                FLAG_HIDE_NAVBAR_WINDOW ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL, null);
+        if (enableTaskbarNoRecreate()) {
             mWindowManager = mContext.getSystemService(WindowManager.class);
             mTaskbarRootLayout = new FrameLayout(mContext) {
                 @Override
@@ -305,10 +294,8 @@
     private void destroyExistingTaskbar() {
         debugWhyTaskbarNotDestroyed("destroyExistingTaskbar: " + mTaskbarActivityContext);
         if (mTaskbarActivityContext != null) {
-            LauncherPrefs.get(mContext).removeListener(mTaskbarPinningPreferenceChangeListener,
-                    TASKBAR_PINNING);
             mTaskbarActivityContext.onDestroy();
-            if (!FLAG_HIDE_NAVBAR_WINDOW || ENABLE_TASKBAR_NO_RECREATION.get()) {
+            if (!FLAG_HIDE_NAVBAR_WINDOW || enableTaskbarNoRecreate()) {
                 mTaskbarActivityContext = null;
             }
         }
@@ -381,7 +368,7 @@
         mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
         Log.d(TASKBAR_NOT_DESTROYED_TAG,
                 "registering activity lifecycle callbacks from setActivity().");
-        mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
+        mActivity.addEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
         UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
                 getUnfoldTransitionProgressProviderForActivity(activity);
         if (unfoldTransitionProgressProvider != null) {
@@ -448,12 +435,14 @@
                 return;
             }
 
-            if (ENABLE_TASKBAR_NO_RECREATION.get() || mTaskbarActivityContext == null) {
+            if (enableTaskbarNoRecreate() || mTaskbarActivityContext == null) {
                 mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp,
                         mNavButtonController, mUnfoldProgressProvider);
             } else {
                 mTaskbarActivityContext.updateDeviceProfile(dp);
             }
+            mSharedState.startTaskbarVariantIsTransient =
+                    DisplayController.isTransientTaskbar(mTaskbarActivityContext);
             mTaskbarActivityContext.init(mSharedState);
 
             if (mActivity != null) {
@@ -461,16 +450,12 @@
                     createTaskbarUIControllerForActivity(mActivity));
             }
 
-            if (ENABLE_TASKBAR_NO_RECREATION.get()) {
+            if (enableTaskbarNoRecreate()) {
                 addTaskbarRootViewToWindow();
                 mTaskbarRootLayout.removeAllViews();
                 mTaskbarRootLayout.addView(mTaskbarActivityContext.getDragLayer());
                 mTaskbarActivityContext.notifyUpdateLayoutParams();
             }
-
-            // We to wait until user unlocks the device to attach listener.
-            LauncherPrefs.get(mContext).addListener(mTaskbarPinningPreferenceChangeListener,
-                TASKBAR_PINNING);
         } finally {
             Trace.endSection();
         }
@@ -559,7 +544,7 @@
             Log.d(TASKBAR_NOT_DESTROYED_TAG,
                     "unregistering activity lifecycle callbacks from "
                             + "removeActivityCallbackAndListeners().");
-            mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
+            mActivity.removeEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
             UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
                     getUnfoldTransitionProgressProviderForActivity(mActivity);
             if (unfoldTransitionProgressProvider != null) {
@@ -603,8 +588,7 @@
     }
 
     private void addTaskbarRootViewToWindow() {
-        if (ENABLE_TASKBAR_NO_RECREATION.get() && !mAddedWindow
-                && mTaskbarActivityContext != null) {
+        if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) {
             mWindowManager.addView(mTaskbarRootLayout,
                     mTaskbarActivityContext.getWindowLayoutParams());
             mAddedWindow = true;
@@ -612,7 +596,7 @@
     }
 
     private void removeTaskbarRootViewFromWindow() {
-        if (ENABLE_TASKBAR_NO_RECREATION.get() && mAddedWindow) {
+        if (enableTaskbarNoRecreate() && mAddedWindow) {
             mWindowManager.removeViewImmediate(mTaskbarRootLayout);
             mAddedWindow = false;
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 533785f..3f72e5d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -52,7 +53,6 @@
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.AssistUtils;
-import com.android.quickstep.views.DesktopTaskView;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -274,7 +274,7 @@
     private void navigateHome() {
         TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
 
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             DesktopVisibilityController desktopVisibilityController =
                     LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
             if (desktopVisibilityController != null) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
new file mode 100644
index 0000000..d1bed3e
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.taskbar
+
+import android.animation.AnimatorSet
+import android.view.View
+import androidx.core.animation.doOnEnd
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN
+import com.android.launcher3.taskbar.TaskbarDividerPopupView.Companion.createAndPopulate
+import java.io.PrintWriter
+
+/** Controls taskbar pinning through a popup view. */
+class TaskbarPinningController(private val context: TaskbarActivityContext) :
+    TaskbarControllers.LoggableTaskbarController {
+
+    private lateinit var controllers: TaskbarControllers
+    private lateinit var taskbarSharedState: TaskbarSharedState
+    private val launcherPrefs = LauncherPrefs.get(context)
+    private val statsLogManager = context.statsLogManager
+    private var isAnimatingTaskbarPinning = false
+
+    fun init(taskbarControllers: TaskbarControllers, sharedState: TaskbarSharedState) {
+        controllers = taskbarControllers
+        taskbarSharedState = sharedState
+    }
+
+    fun showPinningView(view: View) {
+        context.isTaskbarWindowFullscreen = true
+
+        view.post {
+            val popupView = createAndPopulate(view, context)
+            popupView.requestFocus()
+
+            popupView.onCloseCallback =
+                callback@{ didPreferenceChange ->
+                    statsLogManager.logger().log(LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE)
+                    context.dragLayer.post { context.onPopupVisibilityChanged(false) }
+
+                    if (!didPreferenceChange) {
+                        return@callback
+                    }
+                    val animateToValue =
+                        if (!launcherPrefs.get(TASKBAR_PINNING)) {
+                            PINNING_PERSISTENT
+                        } else {
+                            PINNING_TRANSIENT
+                        }
+                    taskbarSharedState.taskbarWasPinned = animateToValue == PINNING_TRANSIENT
+                    animateTaskbarPinning(animateToValue)
+                }
+            context.onPopupVisibilityChanged(true)
+            popupView.show()
+            statsLogManager.logger().log(LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN)
+        }
+    }
+
+    private fun animateTaskbarPinning(animateToValue: Float) {
+        val animatorSet = AnimatorSet()
+        val taskbarViewController = controllers.taskbarViewController
+        val dragLayerController = controllers.taskbarDragLayerController
+
+        animatorSet.playTogether(
+            dragLayerController.taskbarBackgroundProgress.animateToValue(animateToValue),
+            taskbarViewController.taskbarIconTranslationYForPinning.animateToValue(animateToValue),
+            taskbarViewController.taskbarIconScaleForPinning.animateToValue(animateToValue),
+            taskbarViewController.taskbarIconTranslationXForPinning.animateToValue(animateToValue)
+        )
+
+        animatorSet.doOnEnd { recreateTaskbarAndUpdatePinningValue() }
+        animatorSet.duration = PINNING_ANIMATION_DURATION
+        updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(true)
+        animatorSet.start()
+    }
+
+    private fun updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(isAnimating: Boolean) {
+        isAnimatingTaskbarPinning = isAnimating
+        context.dragLayer.setAnimatingTaskbarPinning(isAnimating)
+    }
+
+    private fun recreateTaskbarAndUpdatePinningValue() {
+        updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(false)
+        launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
+    }
+
+    override fun dumpLogs(prefix: String, pw: PrintWriter) {
+        pw.println(prefix + "TaskbarPinningController:")
+        pw.println("$prefix\tisAnimatingTaskbarPinning=$isAnimatingTaskbarPinning")
+        pw.println("$prefix\tTASKBAR_PINNING shared pref =" + launcherPrefs.get(TASKBAR_PINNING))
+    }
+
+    companion object {
+        const val PINNING_PERSISTENT = 1f
+        const val PINNING_TRANSIENT = 0f
+        const val PINNING_ANIMATION_DURATION = 500L
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
index abbd18b..176a8c5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
@@ -71,4 +71,11 @@
             new InsetsFrameProvider(mInsetsOwner, INDEX_RIGHT, systemGestures())
                     .setSource(SOURCE_DISPLAY)
     };
+
+    // Allows us to shift translation logic when doing taskbar pinning animation.
+    public Boolean startTaskbarVariantIsTransient = true;
+
+    // To track if taskbar was pinned using taskbar pinning feature at the time of recreate,
+    // so we can unstash transient taskbar when we un-pinning taskbar.
+    public Boolean taskbarWasPinned = false;
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index e67a6d5..5be74be 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -22,7 +22,7 @@
 import static com.android.app.animation.Interpolators.FINAL_FRAME;
 import static com.android.app.animation.Interpolators.INSTANT;
 import static com.android.app.animation.Interpolators.LINEAR;
-import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
+import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
@@ -337,7 +337,8 @@
                 && mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
         boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible;
         updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
-        updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, isTransientTaskbar);
+        updateStateForFlag(FLAG_STASHED_IN_APP_AUTO,
+                isTransientTaskbar && !mTaskbarSharedState.taskbarWasPinned);
         updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
         updateStateForFlag(FLAG_IN_SETUP, isInSetup);
         updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, isPhoneMode()
@@ -346,6 +347,9 @@
         // us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
         updateStateForFlag(FLAG_IN_APP, true);
         applyState(/* duration = */ 0);
+        if (mTaskbarSharedState.taskbarWasPinned) {
+            tryStartTaskbarTimeout();
+        }
         notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
     }
 
@@ -361,7 +365,7 @@
      * Returns whether the user can manually stash the taskbar based on the current device state.
      */
     protected boolean supportsManualStashing() {
-        if (ENABLE_TASKBAR_PINNING.get() && mPrefs.getBoolean(TASKBAR_PINNING_KEY, false)) {
+        if (ENABLE_TASKBAR_PINNING.get() && LauncherPrefs.get(mActivity).get(TASKBAR_PINNING)) {
             return false;
         }
         return supportsVisualStashing()
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 5f58de5..a7461b7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -20,7 +20,6 @@
 
 import static com.android.launcher3.Flags.enableCursorHoverStates;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
 
 import android.content.Context;
@@ -98,8 +97,6 @@
 
     private final float mTransientTaskbarMinWidth;
 
-    private final float mTaskbarAllAppsButtonTranslationXOffset;
-
     private boolean mShouldTryStartAlign;
 
     public TaskbarView(@NonNull Context context) {
@@ -125,13 +122,16 @@
                 && !TaskbarManager.isPhoneMode(mActivityContext.getDeviceProfile());
         mIsRtl = Utilities.isRtl(resources);
         mTransientTaskbarMinWidth = resources.getDimension(R.dimen.transient_taskbar_min_width);
-        mTaskbarAllAppsButtonTranslationXOffset =
-                resources.getDimension(getAllAppsButtonTranslationXOffset(isTransientTaskbar));
+
 
         onDeviceProfileChanged(mActivityContext.getDeviceProfile());
 
         int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
         int actualIconSize = mActivityContext.getDeviceProfile().taskbarIconSize;
+        if (FeatureFlags.ENABLE_TASKBAR_PINNING.get()) {
+            DeviceProfile deviceProfile = mActivityContext.getTransientTaskbarDeviceProfile();
+            actualIconSize = deviceProfile.taskbarIconSize;
+        }
         int visualIconSize = (int) (actualIconSize * ICON_VISIBLE_AREA_FACTOR);
 
         mIconTouchSize = Math.max(actualIconSize,
@@ -139,8 +139,11 @@
 
         // We layout the icons to be of mIconTouchSize in width and height
         mItemMarginLeftRight = actualMargin - (mIconTouchSize - visualIconSize) / 2;
-        mItemPadding = (mIconTouchSize - actualIconSize) / 2;
 
+        // We always layout taskbar as a transient taskbar when we have taskbar pinning feature on,
+        // then we scale and translate the icons to match persistent taskbar designs, so we use
+        // taskbar icon size from current device profile to calculate correct item padding.
+        mItemPadding = (mIconTouchSize - mActivityContext.getDeviceProfile().taskbarIconSize) / 2;
         mFolderLeaveBehindColor = Themes.getAttrColor(mActivityContext,
                 android.R.attr.textColorTertiary);
 
@@ -173,7 +176,8 @@
 
     @DrawableRes
     private int getAllAppsButton(boolean isTransientTaskbar) {
-        boolean shouldSelectTransientIcon = (isTransientTaskbar || ENABLE_TASKBAR_PINNING.get())
+        boolean shouldSelectTransientIcon =
+                (isTransientTaskbar || FeatureFlags.ENABLE_TASKBAR_PINNING.get())
                 && !mActivityContext.isThreeButtonNav();
         if (ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
             return shouldSelectTransientIcon
@@ -187,7 +191,7 @@
     }
 
     @DimenRes
-    private int getAllAppsButtonTranslationXOffset(boolean isTransientTaskbar) {
+    public int getAllAppsButtonTranslationXOffset(boolean isTransientTaskbar) {
         if (isTransientTaskbar) {
             return R.dimen.transient_taskbar_all_apps_button_translation_x_offset;
         } else {
@@ -370,8 +374,6 @@
         }
 
         if (mAllAppsButton != null) {
-            mAllAppsButton.setTranslationXForTaskbarAllAppsIcon(getChildCount() > 0
-                    ? mTaskbarAllAppsButtonTranslationXOffset : 0f);
             addView(mAllAppsButton, mIsRtl ? getChildCount() : 0);
 
             // if only all apps button present, don't include divider view.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 8a8c3bc..0d06088 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -21,14 +21,19 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.Utilities.mapRange;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.anim.AnimatedFloat.VALUE;
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
+import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
+import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
 import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
 import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_PINNING_ANIM;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM;
 
 import android.animation.Animator;
@@ -61,11 +66,13 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.ThemedIconDrawable;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.views.IconButtonView;
 
 import java.io.PrintWriter;
 import java.util.function.Predicate;
@@ -98,12 +105,27 @@
             this::updateTranslationY);
     private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat(
             this::updateTranslationY);
+
+    private final AnimatedFloat mTaskbarIconScaleForPinning = new AnimatedFloat(
+            this::updateTaskbarIconsScale);
+
+    private final AnimatedFloat mTaskbarIconTranslationXForPinning = new AnimatedFloat(
+            this::updateTaskbarIconTranslationXForPinning);
+
+    private final AnimatedFloat mTaskbarIconTranslationYForPinning = new AnimatedFloat(
+            this::updateTranslationY);
+
+    private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener =
+            (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
+                    -> updateTaskbarIconTranslationXForPinning();
+
+
     private AnimatedFloat mTaskbarNavButtonTranslationY;
     private AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay;
     private float mTaskbarIconTranslationYForSwipe;
     private float mTaskbarIconTranslationYForSpringOnStash;
 
-    private final int mTaskbarBottomMargin;
+    private int mTaskbarBottomMargin;
     private final int mStashedHandleHeight;
     private final int mLauncherThemedIconsBackgroundColor;
     private final int mTaskbarThemedIconsBackgroundColor;
@@ -131,8 +153,18 @@
 
     private final boolean mIsRtl;
 
+    private final DeviceProfile mTransientTaskbarDp;
+    private final DeviceProfile mPersistentTaskbarDp;
+
+    private final int mTransientIconSize;
+    private final int mPersistentIconSize;
+
     public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
         mActivity = activity;
+        mTransientTaskbarDp = mActivity.getTransientTaskbarDeviceProfile();
+        mPersistentTaskbarDp = mActivity.getPersistentTaskbarDeviceProfile();
+        mTransientIconSize = mTransientTaskbarDp.taskbarIconSize;
+        mPersistentIconSize = mPersistentTaskbarDp.taskbarIconSize;
         mTaskbarView = taskbarView;
         mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS);
         mTaskbarIconAlpha.setUpdateVisibility(true);
@@ -162,6 +194,12 @@
                 : mActivity.getDeviceProfile().taskbarHeight;
 
         mTaskbarIconScaleForStash.updateValue(1f);
+        float pinningValue = DisplayController.isTransientTaskbar(mActivity)
+                ? PINNING_TRANSIENT
+                : PINNING_PERSISTENT;
+        mTaskbarIconScaleForPinning.updateValue(pinningValue);
+        mTaskbarIconTranslationYForPinning.updateValue(pinningValue);
+        mTaskbarIconTranslationXForPinning.updateValue(pinningValue);
 
         mModelCallbacks.init(controllers);
         if (mActivity.isUserSetupComplete()) {
@@ -181,6 +219,9 @@
             mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN)
                     .animateToValue(isPhoneButtonNavMode(mActivity) ? 0 : 1).start();
         }
+        if (ENABLE_TASKBAR_PINNING.get()) {
+            mTaskbarView.addOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
+        }
     }
 
     /**
@@ -191,6 +232,9 @@
     }
 
     public void onDestroy() {
+        if (ENABLE_TASKBAR_PINNING.get()) {
+            mTaskbarView.removeOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
+        }
         LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
         mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
         mModelCallbacks.unregisterListeners();
@@ -253,6 +297,18 @@
         return mTaskbarIconTranslationYForStash;
     }
 
+    public AnimatedFloat getTaskbarIconScaleForPinning() {
+        return mTaskbarIconScaleForPinning;
+    }
+
+    public AnimatedFloat getTaskbarIconTranslationXForPinning() {
+        return mTaskbarIconTranslationXForPinning;
+    }
+
+    public AnimatedFloat getTaskbarIconTranslationYForPinning() {
+        return mTaskbarIconTranslationYForPinning;
+    }
+
     /**
      * Applies scale properties for the entire TaskbarView (rather than individual icons).
      */
@@ -263,6 +319,65 @@
     }
 
     /**
+     * Applies scale properties for the taskbar icons
+     */
+    private void updateTaskbarIconsScale() {
+        float scale = mTaskbarIconScaleForPinning.value;
+        View[] iconViews = mTaskbarView.getIconViews();
+
+        float finalScale;
+        if (mControllers.getSharedState().startTaskbarVariantIsTransient) {
+            finalScale = mapRange(scale, 1f, ((float) mPersistentIconSize / mTransientIconSize));
+        } else {
+            finalScale = mapRange(scale, ((float) mTransientIconSize / mPersistentIconSize), 1f);
+        }
+
+        for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) {
+            iconViews[iconIndex].setScaleX(finalScale);
+            iconViews[iconIndex].setScaleY(finalScale);
+        }
+    }
+
+    private void updateTaskbarIconTranslationXForPinning() {
+        View[] iconViews = mTaskbarView.getIconViews();
+        float scale = mTaskbarIconTranslationXForPinning.value;
+        float taskbarCenterX =
+                mTaskbarView.getLeft() + (mTaskbarView.getRight() - mTaskbarView.getLeft()) / 2.0f;
+
+        float finalMarginScale = mapRange(scale, 0f, mTransientIconSize - mPersistentIconSize);
+
+        float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension(
+                mTaskbarView.getAllAppsButtonTranslationXOffset(true));
+        float persistentTaskbarAllAppsOffset = mActivity.getResources().getDimension(
+                mTaskbarView.getAllAppsButtonTranslationXOffset(false));
+
+        float allAppIconTranslateRange = mapRange(scale, transientTaskbarAllAppsOffset,
+                persistentTaskbarAllAppsOffset);
+
+        float halfIconCount = iconViews.length / 2.0f;
+        for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) {
+            View iconView = iconViews[iconIndex];
+            MultiTranslateDelegate translateDelegate =
+                    ((Reorderable) iconView).getTranslateDelegate();
+            float iconCenterX =
+                    iconView.getLeft() + (iconView.getRight() - iconView.getLeft()) / 2.0f;
+            if (iconCenterX <= taskbarCenterX) {
+                translateDelegate.getTranslationX(INDEX_TASKBAR_PINNING_ANIM).setValue(
+                        finalMarginScale * (halfIconCount - iconIndex));
+            } else {
+                translateDelegate.getTranslationX(INDEX_TASKBAR_PINNING_ANIM).setValue(
+                        -finalMarginScale * (iconIndex - halfIconCount));
+            }
+
+            if (iconView.equals(mTaskbarView.getAllAppsButtonView()) && iconViews.length > 1) {
+                ((IconButtonView) iconView).setTranslationXForTaskbarAllAppsIcon(
+                        allAppIconTranslateRange);
+            }
+        }
+    }
+
+
+    /**
      * Sets the translation of the TaskbarView during the swipe up gesture.
      */
     public void setTranslationYForSwipe(float transY) {
@@ -282,10 +397,41 @@
         mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value
                 + mTaskbarIconTranslationYForStash.value
                 + mTaskbarIconTranslationYForSwipe
+                + getTaskbarIconTranslationYForPinningValue()
                 + mTaskbarIconTranslationYForSpringOnStash);
     }
 
     /**
+     * Computes translation y for taskbar pinning.
+     */
+    private float getTaskbarIconTranslationYForPinningValue() {
+        if (mControllers.getSharedState() == null) return 0f;
+
+        float scale = mTaskbarIconTranslationYForPinning.value;
+        float taskbarIconTranslationYForPinningValue;
+
+        // transY is calculated here by adding/subtracting the taskbar bottom margin
+        // aligning the icon bound to be at bottom of current taskbar view and then
+        // finally placing the icon in the middle of new taskbar background height.
+        if (mControllers.getSharedState().startTaskbarVariantIsTransient) {
+            float transY =
+                    mTransientTaskbarDp.taskbarBottomMargin + (mTransientTaskbarDp.taskbarHeight
+                            - mTaskbarView.getIconLayoutBounds().bottom)
+                            - (mPersistentTaskbarDp.taskbarHeight
+                                    - mTransientTaskbarDp.taskbarIconSize) / 2f;
+            taskbarIconTranslationYForPinningValue = mapRange(scale, 0f, transY);
+        } else {
+            float transY =
+                    -mTransientTaskbarDp.taskbarBottomMargin + (mPersistentTaskbarDp.taskbarHeight
+                            - mTaskbarView.getIconLayoutBounds().bottom)
+                            - (mTransientTaskbarDp.taskbarHeight
+                                    - mTransientTaskbarDp.taskbarIconSize) / 2f;
+            taskbarIconTranslationYForPinningValue = mapRange(scale, transY, 0f);
+        }
+        return taskbarIconTranslationYForPinningValue;
+    }
+
+    /**
      * Updates the Taskbar's themed icons background according to the progress between in-app/home.
      */
     protected void updateIconsBackground() {
@@ -471,6 +617,8 @@
         mOnControllerPreCreateCallback.run();
         DeviceProfile taskbarDp = mActivity.getDeviceProfile();
         Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
+        boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
+
         float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.taskbarIconSize;
         int borderSpacing = launcherDp.hotseatBorderSpace;
         int hotseatCellSize = DeviceProfile.calculateCellWidth(
@@ -497,6 +645,10 @@
         setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
                 anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
 
+        mTaskbarBottomMargin = isTransientTaskbar
+                ? mTransientTaskbarDp.taskbarBottomMargin
+                : mPersistentTaskbarDp.taskbarBottomMargin;
+
         for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
             View child = mTaskbarView.getChildAt(i);
             boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonView();
@@ -507,7 +659,7 @@
                 // to avoid icons disappearing rather than fading out visually.
                 setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f));
             } else if ((isAllAppsButton && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get())
-                    || (isTaskbarDividerView && FeatureFlags.ENABLE_TASKBAR_PINNING.get())) {
+                    || (isTaskbarDividerView && ENABLE_TASKBAR_PINNING.get())) {
                 if (!isToHome
                         && mIsHotseatIconOnTopWhenAligned
                         && mIsStashed) {
@@ -528,6 +680,8 @@
                         + launcherDp.hotseatQsbWidth / 2f
                         : hotseatPadding.left - borderSpacing - launcherDp.hotseatQsbWidth / 2f;
                 float childCenter = (child.getLeft() + child.getRight()) / 2f;
+                childCenter += ((Reorderable) child).getTranslateDelegate().getTranslationX(
+                        INDEX_TASKBAR_PINNING_ANIM).getValue();
                 float halfQsbIconWidthDiff =
                         (launcherDp.hotseatQsbWidth - taskbarDp.taskbarIconSize) / 2f;
                 float scale = ((float) taskbarDp.taskbarIconSize)
@@ -561,9 +715,9 @@
             }
 
             int positionInHotseat;
-            if (isAllAppsButton) {
-                // Note that there is no All Apps button in the hotseat, this position is only used
-                // as its convenient for animation purposes.
+            if (isAllAppsButton || isTaskbarDividerView) {
+                // Note that there is no All Apps button or taskbar divider view in the hotseat,
+                // this position is only used as its convenient for animation purposes.
                 positionInHotseat = Utilities.isRtl(child.getResources())
                         ? taskbarDp.numShownHotseatIcons
                         : -1;
@@ -587,10 +741,11 @@
                         + hotseatCellSize / 2f;
             }
             float childCenter = (child.getLeft() + child.getRight()) / 2f;
+            childCenter += ((Reorderable) child).getTranslateDelegate().getTranslationX(
+                    INDEX_TASKBAR_PINNING_ANIM).getValue();
             float toX = hotseatIconCenter - childCenter;
             if (child instanceof Reorderable) {
                 MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
-
                 setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
                         MULTI_PROPERTY_VALUE, toX, interpolator);
                 setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM),
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index d78ca88..e2f4f32 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -107,8 +107,7 @@
         setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
 
-        boolean exitingOverview = !FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
-                && !toState.overviewUi;
+        boolean exitingOverview = !FeatureFlags.enableSplitContextually() && !toState.overviewUi;
         if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
             setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
                     .createPlaceholderDismissAnim(mLauncher));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 4c739fa..22ab79d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -19,7 +19,6 @@
 import static android.os.Trace.TRACE_TAG_APP;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
-
 import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
 import static com.android.launcher3.LauncherSettings.Animation.VIEW_BACKGROUND;
@@ -52,6 +51,7 @@
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
 
@@ -165,7 +165,6 @@
 import com.android.quickstep.util.SplitToWorkspaceController;
 import com.android.quickstep.util.SplitWithKeyboardShortcutController;
 import com.android.quickstep.util.TISBindHelper;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.FloatingTaskView;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
@@ -247,7 +246,7 @@
                         getDepthController(), getStatsLogManager(),
                         systemUiProxy, RecentsModel.INSTANCE.get(this),
                         () -> onStateBack());
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
                     getStateManager(), systemUiProxy, getIApplicationThread(),
                     getDepthController());
@@ -265,7 +264,7 @@
         mAppTransitionManager.registerRemoteAnimations();
         mAppTransitionManager.registerRemoteTransitions();
 
-        if (ENABLE_HOME_TRANSITION_LISTENER.get()) {
+        if (FeatureFlags.enableHomeTransitionListener()) {
             mHomeTransitionController = new HomeTransitionController(this);
             mHomeTransitionController.registerHomeTransitionListener();
         }
@@ -273,7 +272,7 @@
         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
         mDepthController = new DepthController(this);
         mDesktopVisibilityController = new DesktopVisibilityController(this);
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             mDesktopVisibilityController.registerSystemUiListener();
             mSplitSelectStateController.initSplitFromDesktopController(this);
         }
@@ -377,7 +376,7 @@
         }
 
         if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) {
-            if (!ENABLE_HOME_TRANSITION_LISTENER.get() && mTaskbarUIController != null) {
+            if (!FeatureFlags.enableHomeTransitionListener() && mTaskbarUIController != null) {
                 mTaskbarUIController.onLauncherVisibilityChanged(hasBeenResumed());
             }
         }
@@ -644,7 +643,7 @@
                     splitSelectSource.alreadyRunningTaskId = taskWasFound
                             ? foundTask.key.id
                             : INVALID_TASK_ID;
-                    if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+                    if (FeatureFlags.enableSplitContextually()) {
                         startSplitToHome(splitSelectSource);
                     } else {
                         recentsView.initiateSplitSelect(splitSelectSource);
@@ -718,7 +717,7 @@
 
         super.onPause();
 
-        if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+        if (FeatureFlags.enableSplitContextually()) {
             // If Launcher pauses before both split apps are selected, exit split screen.
             if (!mSplitSelectStateController.isBothSplitAppsConfirmed() &&
                     !mSplitSelectStateController.isLaunchingFirstAppFullscreen()) {
@@ -890,7 +889,7 @@
 
     @Override
     public void setResumed() {
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             DesktopVisibilityController controller = mDesktopVisibilityController;
             if (controller != null && controller.areFreeformTasksVisible()
                     && !controller.isRecentsGestureInProgress()) {
@@ -1184,11 +1183,6 @@
     }
 
     @Override
-    public void tryClearAccessibilityFocus(View view) {
-        view.clearAccessibilityFocus();
-    }
-
-    @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
index 396f94f..36d62c6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
@@ -392,8 +392,8 @@
         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.getOnPreferenceChangeListener().onPreferenceChange(seekBarPref, value);
-
+        seekBarPref.setSummary(String.valueOf(scale == 1 ? value
+                : value / (float) scale));
         return seekBarPref;
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index e578720..d11a08b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
 import android.content.Context;
 import android.graphics.Color;
@@ -26,7 +27,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -90,7 +90,7 @@
 
     @Override
     protected float getDepthUnchecked(Context context) {
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             if (Launcher.getLauncher(context).areFreeformTasksVisible()) {
                 // Don't blur the background while freeform tasks are visible
                 return 0;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index 7392469..ba44d6a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
 import android.graphics.Color;
 
@@ -23,7 +24,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.util.Themes;
-import com.android.quickstep.views.DesktopTaskView;
 
 /**
  * State to indicate we are about to launch a recent task. Note that this state is only used when
@@ -46,7 +46,7 @@
 
     @Override
     public int getWorkspaceScrimColor(Launcher launcher) {
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             if (launcher.areFreeformTasksVisible()) {
                 // No scrim while freeform tasks are visible
                 return Color.TRANSPARENT;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index b266bcd..f6cd30a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -195,7 +195,7 @@
                     () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
             if (mStartState.overviewUi) {
                 new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState),
-                        FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
+                        FeatureFlags.enableSplitContextually()
                                 ? mCancelSplitRunnable
                                 : null)
                         .animateWithVelocity(velocity);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 6f421eb..968faf0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static android.view.MotionEvent.ACTION_DOWN;
-
 import static com.android.app.animation.Interpolators.ACCELERATE_0_75;
 import static com.android.app.animation.Interpolators.DECELERATE_3;
 import static com.android.app.animation.Interpolators.LINEAR;
@@ -49,6 +48,7 @@
 import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
@@ -83,7 +83,6 @@
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.WorkspaceRevealAnim;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@@ -177,7 +176,7 @@
         if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
             return false;
         }
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             // TODO(b/268075592): add support for quickswitch to/from desktop
             return false;
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 9a35bb2..ff142fe 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -30,6 +30,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
@@ -48,7 +49,6 @@
 import com.android.launcher3.util.NavigationMode;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
@@ -79,7 +79,7 @@
         if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
             return false;
         }
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             // TODO(b/268075592): add support for quickswitch to/from desktop
             return false;
         }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 4dfa81d..b06a978 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -20,10 +20,11 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.widget.Toast.LENGTH_SHORT;
-
 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
 import static com.android.app.animation.Interpolators.DECELERATE;
 import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
+import static com.android.launcher3.BaseActivity.EVENT_STARTED;
 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.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
@@ -54,6 +55,7 @@
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.INVALID_VELOCITY_ON_SWIPE_UP;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.LAUNCHER_DESTROYED;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
@@ -62,7 +64,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
-import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.TaskInfo;
 import android.app.WindowConfiguration;
@@ -89,6 +90,7 @@
 import android.widget.Toast;
 import android.window.PictureInPictureSurfaceTransaction;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
@@ -109,7 +111,6 @@
 import com.android.launcher3.taskbar.TaskbarThresholdUtils;
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.TraceHelper;
@@ -132,7 +133,6 @@
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.SwipePipToHomeAnimator;
 import com.android.quickstep.util.TaskViewSimulator;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
@@ -184,19 +184,13 @@
     protected MultiStateCallback mStateCallback;
     protected boolean mCanceled;
     private boolean mRecentsViewScrollLinked = false;
-    private final ActivityLifecycleCallbacksAdapter mLifecycleCallbacks =
-            new ActivityLifecycleCallbacksAdapter() {
-                @Override
-                public void onActivityDestroyed(Activity activity) {
-                    if (mActivity != activity) {
-                        return;
-                    }
-                    ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
-                    mRecentsView = null;
-                    mActivity = null;
-                    mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
-                }
-            };
+
+    private final Runnable mLauncherOnDestroyCallback = () -> {
+        ActiveGestureLog.INSTANCE.addLog("Launcher destroyed", LAUNCHER_DESTROYED);
+        mRecentsView = null;
+        mActivity = null;
+        mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
+    };
 
     private static int FLAG_COUNT = 0;
     private static int getNextStateFlag(String name) {
@@ -317,8 +311,9 @@
     private final int mSplashMainWindowShiftLength;
 
     private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
+    private final Runnable mLauncherOnStartCallback = this::onLauncherStart;
 
-    private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
+    @Nullable private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
     protected boolean mIsSwipingPipToHome;
     // TODO(b/195473090) no split PIP for now, remove once we have more clarity
     //  can try to have RectFSpringAnim evaluate multiple rects at once
@@ -490,6 +485,7 @@
                 mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
                 return true;
             }
+            resetLauncherListeners();
 
             // The launcher may have been recreated as a result of device rotation.
             int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES;
@@ -513,7 +509,7 @@
         if (alreadyOnHome) {
             onLauncherStart();
         } else {
-            activity.runOnceOnStart(this::onLauncherStart);
+            activity.addEventCallback(EVENT_STARTED, mLauncherOnStartCallback);
         }
 
         // Set up a entire animation lifecycle callback to notify the current recents view when
@@ -538,9 +534,8 @@
 
         setupRecentsViewUi();
         mRecentsView.runOnPageScrollsInitialized(this::linkRecentsViewScroll);
-        activity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
-
-        mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
+        mActivity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
+        mActivity.addEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
         return true;
     }
 
@@ -677,6 +672,9 @@
         if (Arrays.stream(runningTasks).anyMatch(Objects::isNull)) {
             return;
         }
+        if (mRecentsView == null) {
+            return;
+        }
         mRecentsView.onGestureAnimationStart(runningTasks, mDeviceState.getRotationTouchHelper());
     }
 
@@ -936,7 +934,7 @@
     public void onRecentsAnimationStart(RecentsAnimationController controller,
             RecentsAnimationTargets targets) {
         super.onRecentsAnimationStart(controller, targets);
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && targets.hasDesktopTasks()) {
+        if (isDesktopModeSupported() && targets.hasDesktopTasks()) {
             mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);
         } else {
             int untrimmedAppCount = mRemoteTargetHandles.length;
@@ -1162,7 +1160,7 @@
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
                 // Notify the SysUI to use fade-in animation when entering PiP
                 SystemUiProxy.INSTANCE.get(mContext).setPipAnimationTypeToAlpha();
-                if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+                if (isDesktopModeSupported()) {
                     // Notify the SysUI to stash desktop apps if they are visible
                     DesktopVisibilityController desktopVisibilityController =
                             mActivityInterface.getDesktopVisibilityController();
@@ -1247,7 +1245,7 @@
             return LAST_TASK;
         }
 
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && endTarget == NEW_TASK) {
+        if (isDesktopModeSupported() && endTarget == NEW_TASK) {
             // TODO(b/268075592): add support for quickswitch to/from desktop
             return LAST_TASK;
         }
@@ -1264,8 +1262,8 @@
             return isSwipeUp ? ALL_APPS : LAST_TASK;
         }
         if (!isSwipeUp) {
-            final boolean isCenteredOnNewTask =
-                    mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
+            final boolean isCenteredOnNewTask = mRecentsView != null
+                    && mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
             return willGoToNewTask || isCenteredOnNewTask ? NEW_TASK : LAST_TASK;
         }
 
@@ -1541,11 +1539,13 @@
             HomeAnimationFactory homeAnimFactory =
                     createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
                             runningTaskTarget);
-            mIsSwipingPipToHome = !mIsSwipeForSplit && appCanEnterPip;
+            SwipePipToHomeAnimator swipePipToHomeAnimator = !mIsSwipeForSplit && appCanEnterPip
+                    ? createWindowAnimationToPip(homeAnimFactory, runningTaskTarget, start)
+                    : null;
+            mIsSwipingPipToHome = swipePipToHomeAnimator != null;
             final RectFSpringAnim[] windowAnim;
             if (mIsSwipingPipToHome) {
-                mSwipePipToHomeAnimator = createWindowAnimationToPip(
-                        homeAnimFactory, runningTaskTarget, start);
+                mSwipePipToHomeAnimator = swipePipToHomeAnimator;
                 mSwipePipToHomeAnimators[0] = mSwipePipToHomeAnimator;
                 if (mSwipePipToHomeReleaseCheck != null) {
                     mSwipePipToHomeReleaseCheck.setCanRelease(false);
@@ -1553,6 +1553,9 @@
 
                 // grab a screenshot before the PipContentOverlay gets parented on top of the task
                 UI_HELPER_EXECUTOR.execute(() -> {
+                    if (mRecentsAnimationController == null) {
+                        return;
+                    }
                     // Directly use top task, split to pip handled on shell side
                     final int taskId = mGestureState.getTopRunningTaskId();
                     mTaskSnapshotCache.put(taskId,
@@ -1670,6 +1673,10 @@
     @Nullable
     private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
             RemoteAnimationTarget runningTaskTarget, float startProgress) {
+        if (mRecentsView == null) {
+            // Overview was destroyed, bail early.
+            return null;
+        }
         // Directly animate the app to PiP (picture-in-picture) mode
         final ActivityManager.RunningTaskInfo taskInfo = runningTaskTarget.taskInfo;
         final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
@@ -1810,7 +1817,8 @@
     private void continueComputingRecentsScrollIfNecessary() {
         if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)
                 && !mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)
-                && !mCanceled) {
+                && !mCanceled
+                && mRecentsView != null) {
             computeRecentsScrollIfInvisible();
             mRecentsView.postOnAnimation(this::continueComputingRecentsScrollIfNecessary);
         }
@@ -1854,7 +1862,6 @@
         if (mActivity != null) {
             // In the off chance that the gesture ends before Launcher is started, we should clear
             // the callback here so that it doesn't update with the wrong state
-            mActivity.clearRunOnceOnStartCallback();
             resetLauncherListeners();
         }
         if (mGestureState.isRecentsAnimationRunning() && mGestureState.getEndTarget() != null
@@ -1920,7 +1927,7 @@
     private void reset() {
         mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
         if (mActivity != null) {
-            mActivity.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);
+            mActivity.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
         }
     }
 
@@ -1991,6 +1998,9 @@
      * continued quick switch gesture, which cancels the previous handler but doesn't invalidate it.
      */
     private void resetLauncherListeners() {
+        mActivity.removeEventCallback(EVENT_STARTED, mLauncherOnStartCallback);
+        mActivity.removeEventCallback(EVENT_DESTROYED, mLauncherOnDestroyCallback);
+
         mActivity.getRootView().setOnApplyWindowInsetsListener(null);
 
         if (mRecentsView != null) {
@@ -2112,7 +2122,7 @@
      * from Launcher to WM.
      */
     private void maybeAbortSwipePipToHome() {
-        if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
+        if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
             SystemUiProxy.INSTANCE.get(mContext).abortSwipePipToHome(
                     mSwipePipToHomeAnimator.getTaskId(),
                     mSwipePipToHomeAnimator.getComponentName());
@@ -2126,7 +2136,10 @@
      * This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
      */
     private void maybeFinishSwipePipToHome() {
-        if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
+        if (mRecentsAnimationController == null) {
+            return;
+        }
+        if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
             mRecentsAnimationController.setFinishTaskTransaction(
                     mSwipePipToHomeAnimator.getTaskId(),
                     mSwipePipToHomeAnimator.getFinishTransaction(),
@@ -2150,7 +2163,7 @@
     protected abstract void finishRecentsControllerToHome(Runnable callback);
 
     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
-        if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
+        if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED) || mRecentsView == null) {
             return;
         }
         endLauncherTransitionController();
@@ -2184,6 +2197,9 @@
     }
 
     protected void linkRecentsViewScroll() {
+        if (mRecentsView == null) {
+            return;
+        }
         SurfaceTransactionApplier applier = new SurfaceTransactionApplier(mRecentsView);
         runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
                         .setSyncTransactionApplier(applier));
@@ -2191,9 +2207,13 @@
                 mRecentsAnimationTargets.addReleaseCheck(applier));
 
         mRecentsView.addOnScrollChangedListener(mOnRecentsScrollListener);
-        runOnRecentsAnimationAndLauncherBound(() ->
-                mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
-                        mRecentsAnimationTargets));
+        runOnRecentsAnimationAndLauncherBound(() -> {
+            if (mRecentsView == null) {
+                return;
+            }
+            mRecentsView.setRecentsAnimationTargets(
+                    mRecentsAnimationController, mRecentsAnimationTargets);
+        });
 
         // Disable scrolling in RecentsView for trackpad 3-finger swipe up gesture.
         if (!mGestureState.isThreeFingerTrackpadGesture()) {
@@ -2300,7 +2320,7 @@
     }
 
     @Override
-    public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
+    public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
         if (mRecentsAnimationController != null) {
             boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch(
                     mGestureState.mLastStartedTaskIdPredicate);
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 88933f4..2dd6a29 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -25,6 +25,7 @@
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
@@ -65,7 +66,6 @@
 import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
@@ -111,7 +111,7 @@
         if (endTarget != null) {
             // We were on our way to this state when we got canceled, end there instead.
             startState = stateFromGestureEndTarget(endTarget);
-            if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+            if (isDesktopModeSupported()) {
                 DesktopVisibilityController controller = getDesktopVisibilityController();
                 if (controller != null && controller.areFreeformTasksVisible()
                         && endTarget == LAST_TASK) {
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 3bc77ff..7c263b8 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -20,7 +20,7 @@
 
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
-import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM;
 
 import android.annotation.TargetApi;
@@ -273,7 +273,7 @@
 
         int numVisibleTasks = 0;
         for (GroupedRecentTaskInfo rawTask : rawTasks) {
-            if (DESKTOP_MODE_SUPPORTED && rawTask.getType() == TYPE_FREEFORM) {
+            if (isDesktopModeSupported() && rawTask.getType() == TYPE_FREEFORM) {
                 GroupTask desktopTask = createDesktopTask(rawTask);
                 allTasks.add(desktopTask);
                 continue;
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 38e896e..961d8c0 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -18,7 +18,6 @@
 import static android.os.Trace.TRACE_TAG_APP;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
-
 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
@@ -26,6 +25,7 @@
 import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -81,7 +81,6 @@
 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.TISBindHelper;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -142,7 +141,7 @@
                         systemUiProxy, RecentsModel.INSTANCE.get(this),
                         null /*activityBackCallback*/);
         mDragLayer.recreateControllers();
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
                     getStateManager(), systemUiProxy, getIApplicationThread(),
                     null /* depthController */
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 3fdc7ba..8d26fd4 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
 import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
 import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
@@ -60,6 +61,8 @@
 import androidx.annotation.BinderThread;
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.LauncherPrefs;
+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;
@@ -86,8 +89,8 @@
     static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
 
     // TODO: Move to quickstep contract
-    private static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 9;
-    private static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 2;
+    private static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 3f;
+    private static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 1.414f;
 
     private final Context mContext;
     private final DisplayController mDisplayController;
@@ -546,15 +549,31 @@
 
     /**
      * Returns the touch slop for {@link InputConsumer}s to compare against before pilfering
-     * pointers. Note that this is squared because it expects to be compared against
-     * {@link com.android.launcher3.Utilities#squaredHypot} (to avoid square root on each event).
+     * pointers.
      */
-    public float getSquaredTouchSlop() {
+    public float getTouchSlop() {
         float slopMultiplier = isFullyGesturalNavMode()
                 ? QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL
                 : QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON;
         float touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
-        return slopMultiplier * touchSlop * touchSlop;
+
+        if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
+            float customSlopMultiplier =
+                    LauncherPrefs.get(mContext).get(LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE) / 100f;
+            return customSlopMultiplier * slopMultiplier * touchSlop;
+        } else {
+            return slopMultiplier * touchSlop;
+        }
+    }
+
+    /**
+     * Returns the squared touch slop for {@link InputConsumer}s to compare against before pilfering
+     * pointers. Note that this is squared because it expects to be compared against
+     * {@link com.android.launcher3.Utilities#squaredHypot} (to avoid square root on each event).
+     */
+    public float getSquaredTouchSlop() {
+        float touchSlop = getTouchSlop();
+        return touchSlop * touchSlop;
     }
 
     public String getSystemUiStateString() {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index 67c56e3..556dd7e 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -17,14 +17,13 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
 import android.app.WindowConfiguration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.RemoteAnimationTarget;
 
-import com.android.quickstep.views.DesktopTaskView;
-
 /**
  * Extension of {@link RemoteAnimationTargets} with additional information about swipe
  * up animation
@@ -53,7 +52,7 @@
      * @return {@code true} if at least one target app is a desktop task
      */
     public boolean hasDesktopTasks() {
-        if (!DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (!isDesktopModeSupported()) {
             return false;
         }
         for (RemoteAnimationTarget target : apps) {
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 3af5ab7..98d0ece 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -17,6 +17,7 @@
 package com.android.quickstep;
 
 import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
 
 import android.content.Context;
@@ -30,7 +31,6 @@
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.wm.shell.util.SplitBounds;
 
 import java.util.ArrayList;
@@ -61,7 +61,7 @@
      * running tasks
      */
     public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             // TODO(279931899): binder call, only for prototyping. Creating the gluer should be
             //  postponed so we can create it when we have the remote animation targets ready.
             int desktopTasks = SystemUiProxy.INSTANCE.get(context).getVisibleDesktopTaskCount(
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 468d96a..038a022 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -17,7 +17,6 @@
 
 import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_HOME_TRANSITION_LISTENER;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
@@ -63,6 +62,7 @@
 import com.android.internal.logging.InstanceId;
 import com.android.internal.util.ScreenshotRequest;
 import com.android.internal.view.AppearanceRegion;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Preconditions;
 import com.android.quickstep.util.ActiveGestureLog;
@@ -1048,7 +1048,7 @@
     }
 
     public void setHomeTransitionListener(IHomeTransitionListener listener) {
-        if (!ENABLE_HOME_TRANSITION_LISTENER.get()) {
+        if (!FeatureFlags.enableHomeTransitionListener()) {
             return;
         }
 
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index e8adcab..96e39c4 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -20,6 +20,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
 import android.app.Activity;
 import android.app.ActivityOptions;
@@ -29,7 +30,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.os.SystemProperties;
 import android.provider.Settings;
 import android.util.Log;
 import android.view.View;
@@ -351,7 +351,7 @@
             return Settings.Global.getInt(
                         activity.getContentResolver(),
                         Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0
-                    && !SystemProperties.getBoolean("persist.wm.debug.desktop_mode_2", false);
+                    && !isDesktopModeSupported();
         }
     };
 
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 819f249..662a895 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -39,7 +39,7 @@
 import static com.android.launcher3.QuickstepTransitionManager.SPLIT_LAUNCH_DURATION;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
-import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -185,7 +185,7 @@
             // Re-use existing handles
             remoteTargetHandles = recentsViewHandles;
         } else {
-            boolean forDesktop = DESKTOP_MODE_SUPPORTED && v instanceof DesktopTaskView;
+            boolean forDesktop = isDesktopModeSupported() && v instanceof DesktopTaskView;
             RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(),
                     recentsView.getSizeStrategy(), targets, forDesktop);
             if (forDesktop) {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index b2f04b8..98a9938 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -963,7 +963,8 @@
                         .append(SUBSTRING_PREFIX)
                         .append("Long press nav handle enabled, ")
                         .append("using NavHandleLongPressInputConsumer");
-                base = new NavHandleLongPressInputConsumer(this, base, mInputMonitorCompat);
+                base = new NavHandleLongPressInputConsumer(this, base, mInputMonitorCompat,
+                        mDeviceState);
             }
 
             if (mDeviceState.isBubblesExpanded()) {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 059b0ce..95ce406 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -254,7 +254,7 @@
             setOverviewSelectEnabled(false);
         }
         if (finalState != OVERVIEW_SPLIT_SELECT) {
-            if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+            if (FeatureFlags.enableSplitContextually()) {
                 mSplitSelectStateController.resetState();
             } else {
                 resetFromSplitSelectionState();
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index f11e537..32f7bd5 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.inputconsumers;
 
-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.util.Executors.MAIN_EXECUTOR;
 
@@ -28,6 +27,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.InputConsumer;
+import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 /**
@@ -47,23 +47,18 @@
     private MotionEvent mCurrentDownEvent;
 
     public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
-            InputMonitorCompat inputMonitor) {
+            InputMonitorCompat inputMonitor, RecentsAnimationDeviceState deviceState) {
         super(delegate, inputMonitor);
         mViewConfiguration = ViewConfiguration.get(context);
         mNavHandleWidth = context.getResources().getDimensionPixelSize(
                 R.dimen.navigation_home_handle_width);
         mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
-        float touchSlop;
         if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
-            float customSlopMultiplier =
-                    LauncherPrefs.get(context).get(LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE) / 100f;
-            touchSlop = mViewConfiguration.getScaledEdgeSlop() * customSlopMultiplier;
             mLongPressTimeout = LauncherPrefs.get(context).get(LONG_PRESS_NAV_HANDLE_TIMEOUT_MS);
         } else {
-            touchSlop = mViewConfiguration.getScaledTouchSlop();
             mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
         }
-        mTouchSlopSquared = touchSlop * touchSlop;
+        mTouchSlopSquared = deviceState.getSquaredTouchSlop();
         mNavHandleLongPressHandler = NavHandleLongPressHandler.newInstance(context);
     }
 
@@ -87,17 +82,14 @@
                 }
             }
             case MotionEvent.ACTION_MOVE -> {
+                if (!MAIN_EXECUTOR.getHandler().hasCallbacks(mTriggerLongPress)) {
+                    break;
+                }
+
                 float touchSlopSquared = mTouchSlopSquared;
                 float dx = ev.getX() - mCurrentDownEvent.getX();
                 float dy = ev.getY() - mCurrentDownEvent.getY();
                 double distanceSquared = (dx * dx) + (dy * dy);
-                // If the gesture is ambiguous then require more movement before classifying this
-                // as a NON long press gesture.
-                if (ev.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE) {
-                    float ambiguousGestureMultiplier =
-                            mViewConfiguration.getScaledAmbiguousGestureMultiplier();
-                    touchSlopSquared *= ambiguousGestureMultiplier * ambiguousGestureMultiplier;
-                }
                 if (distanceSquared > touchSlopSquared) {
                     cancelLongPress();
                 }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 28c00eb..e724547 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -40,7 +40,6 @@
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
 
 import androidx.annotation.UiThread;
 
@@ -154,7 +153,7 @@
         boolean continuingPreviousGesture = mTaskAnimationManager.isRecentsAnimationRunning();
         mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
 
-        mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
+        mTouchSlop = mDeviceState.getTouchSlop();
         mSquaredTouchSlop = mDeviceState.getSquaredTouchSlop();
 
         mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index ae497f0..6ee65d4 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -300,7 +300,7 @@
 
         pendingAnimation.addEndListener {
             splitSelectStateController.launchInitialAppFullscreen {
-                if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+                if (FeatureFlags.enableSplitContextually()) {
                     splitSelectStateController.resetState()
                 } else if (resetCallback.isPresent) {
                     resetCallback.get().run()
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 8e3b5ec..145707b 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -31,6 +31,7 @@
 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_PENDINGINTENT;
 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT;
 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
 
 import android.animation.Animator;
@@ -91,7 +92,6 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.TaskViewUtils;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.FloatingTaskView;
 import com.android.quickstep.views.GroupedTaskView;
 import com.android.quickstep.views.RecentsView;
@@ -152,8 +152,7 @@
     private final BackPressHandler mSplitBackHandler = new BackPressHandler() {
         @Override
         public boolean canHandleBack() {
-            return FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get() &&
-                    isSplitSelectActive();
+            return FeatureFlags.enableSplitContextually() && isSplitSelectActive();
         }
 
         @Override
@@ -711,7 +710,7 @@
 
     /**
      * To be called whenever we exit split selection state. If
-     * {@link FeatureFlags#ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE} is set, this should be the
+     * {@link FeatureFlags#enableSplitContextually()} is set, this should be the
      * central way split is getting reset, which should then go through the callbacks to reset
      * other state.
      */
@@ -815,7 +814,7 @@
                 @Override
                 public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
                         int splitPosition, Rect taskBounds) {
-                    if (!DesktopTaskView.DESKTOP_MODE_SUPPORTED) return false;
+                    if (!isDesktopModeSupported()) return false;
                     MAIN_EXECUTOR.execute(() -> enterSplitSelect(taskInfo, splitPosition,
                             taskBounds));
                     return true;
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index 00b5621..3748a24 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -17,8 +17,8 @@
 package com.android.quickstep.util;
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -40,11 +40,11 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.FloatingTaskView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@@ -192,8 +192,8 @@
 
     private boolean shouldIgnoreSecondSplitLaunch() {
         return (!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get()
-                && !ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
-                && !DesktopTaskView.DESKTOP_MODE_SUPPORTED)
+                && !FeatureFlags.enableSplitContextually()
+                && !isDesktopModeSupported())
                 || !mController.isSplitSelectActive();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
index bb028a7..df5765c 100644
--- a/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
+++ b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
@@ -25,6 +25,8 @@
 import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewRootImpl;
 
+import androidx.annotation.NonNull;
+
 import com.android.quickstep.RemoteAnimationTargets.ReleaseCheck;
 
 /**
@@ -48,7 +50,7 @@
     /**
      * @param targetView The view in the surface that acts as synchronization anchor.
      */
-    public SurfaceTransactionApplier(View targetView) {
+    public SurfaceTransactionApplier(@NonNull View targetView) {
         if (targetView.isAttachedToWindow()) {
             initialize(targetView);
         } else {
diff --git a/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
index d7b3431..cdadd71 100644
--- a/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
+++ b/quickstep/src/com/android/quickstep/util/TaskRemovedDuringLaunchListener.java
@@ -18,11 +18,13 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 
-import android.app.Activity;
+import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
+import static com.android.launcher3.BaseActivity.EVENT_RESUMED;
+import static com.android.launcher3.BaseActivity.EVENT_STOPPED;
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
+import com.android.launcher3.BaseActivity;
 import com.android.quickstep.RecentsModel;
 
 /**
@@ -34,19 +36,28 @@
  * If we hit either of those signals and the task is no longer valid, then the registered failure
  * callback will be notified.
  */
-public class TaskRemovedDuringLaunchListener implements ActivityLifecycleCallbacksAdapter {
+public class TaskRemovedDuringLaunchListener {
 
-    private Activity mActivity;
+    private BaseActivity mActivity;
     private int mLaunchedTaskId = INVALID_TASK_ID;
     private Runnable mTaskLaunchFailedCallback = null;
 
+    private final Runnable mUnregisterCallback = this::unregister;
+    private final Runnable mResumeCallback = this::checkTaskLaunchFailed;
+
     /**
      * Registers a failure listener callback if it detects a scenario in which an app launch
      * failed before the transition finished.
      */
-    public void register(Activity activity, int launchedTaskId,
+    public void register(BaseActivity activity, int launchedTaskId,
             @NonNull Runnable taskLaunchFailedCallback) {
-        activity.registerActivityLifecycleCallbacks(this);
+        // The normal task launch case, Launcher stops and updates its state correctly
+        activity.addEventCallback(EVENT_STOPPED, mUnregisterCallback);
+        // The transition hasn't finished but Launcher was resumed, check if the launch failed
+        activity.addEventCallback(EVENT_RESUMED, mResumeCallback);
+        // If we somehow don't get any of the above signals, then just unregister this listener
+        activity.addEventCallback(EVENT_DESTROYED, mUnregisterCallback);
+
         mActivity = activity;
         mLaunchedTaskId = launchedTaskId;
         mTaskLaunchFailedCallback = taskLaunchFailedCallback;
@@ -56,7 +67,10 @@
      * Unregisters the failure listener.
      */
     private void unregister() {
-        mActivity.unregisterActivityLifecycleCallbacks(this);
+        mActivity.removeEventCallback(EVENT_STOPPED, mUnregisterCallback);
+        mActivity.removeEventCallback(EVENT_RESUMED, mResumeCallback);
+        mActivity.removeEventCallback(EVENT_DESTROYED, mUnregisterCallback);
+
         mActivity = null;
         mLaunchedTaskId = INVALID_TASK_ID;
         mTaskLaunchFailedCallback = null;
@@ -70,24 +84,6 @@
         checkTaskLaunchFailed();
     }
 
-    @Override
-    public void onActivityStopped(Activity activity) {
-        // The normal task launch case, Launcher stops and updates its state correctly
-        unregister();
-    }
-
-    @Override
-    public void onActivityResumed(Activity activity) {
-        // The transition hasn't finished but Launcher was resumed, check if the launch failed
-        checkTaskLaunchFailed();
-    }
-
-    @Override
-    public void onActivityDestroyed(Activity activity) {
-        // If we somehow don't get any of the above signals, then just unregister this listener
-        unregister();
-    }
-
     private void checkTaskLaunchFailed() {
         if (mLaunchedTaskId != INVALID_TASK_ID) {
             final int launchedTaskId = mLaunchedTaskId;
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 767aa15..5d8e53e 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -17,6 +17,7 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -108,6 +109,7 @@
     private boolean mIsDesktopTask;
     private int mTaskRectTranslationX;
     private int mTaskRectTranslationY;
+    private int mPivotOffsetX;
 
     public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
         mContext = context;
@@ -179,9 +181,10 @@
             // Ensure the task rect is inside the full task rect
             mTaskRect.offset(fullTaskSize.left, fullTaskSize.top);
         } else {
-            fullTaskSize = mTaskRect;
+            fullTaskSize = new Rect(mTaskRect);
+            mTaskRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
         }
-        fullTaskSize.offset(mTaskRectTranslationX, mTaskRectTranslationY);
+        fullTaskSize.offset(mTaskRectTranslationX + mPivotOffsetX, mTaskRectTranslationY);
         return mOrientationState.getFullScreenScaleAndPivot(fullTaskSize, mDp, mPivot);
     }
 
@@ -265,6 +268,11 @@
      */
     public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) {
         pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
+        if (enableGridOnlyOverview() && mDp.isTablet) {
+            int translationXToMiddle = mDp.widthPx / 2 - mTaskRect.centerX();
+            taskPrimaryTranslation.value = translationXToMiddle;
+            mPivotOffsetX = translationXToMiddle;
+        }
         pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, interpolator);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 9ff990e..a10d2ed 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -17,7 +17,6 @@
 package com.android.quickstep.views;
 
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 
 import android.content.Context;
@@ -52,6 +51,7 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.wm.shell.Flags;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -68,10 +68,11 @@
 // TODO(b/249371338): TaskView needs to be refactored to have better support for N tasks.
 public class DesktopTaskView extends TaskView {
 
-    /** Flag to indicate whether desktop windowing proto 2 is enabled */
-    public static final boolean DESKTOP_MODE_SUPPORTED = SystemProperties.getBoolean(
+    private static final boolean DESKTOP_MODE_SUPPORTED = SystemProperties.getBoolean(
             "persist.wm.debug.desktop_mode_2", false);
 
+    private static final boolean ENABLE_DESKTOP_WINDOWING = Flags.enableDesktopWindowing();
+
     private static final String TAG = DesktopTaskView.class.getSimpleName();
 
     private static final boolean DEBUG = false;
@@ -90,6 +91,17 @@
 
     private View mBackgroundView;
 
+    /** Check whether desktop windowing is enabled */
+    public static boolean isDesktopModeSupported() {
+        // Check for aconfig flag first
+        if (ENABLE_DESKTOP_WINDOWING) {
+            return true;
+        }
+        // Fall back to sysprop flag
+        // TODO(b/304778354): remove sysprop once desktop aconfig flag supports dynamic overriding
+        return DESKTOP_MODE_SUPPORTED;
+    }
+
     public DesktopTaskView(Context context) {
         this(context, null);
     }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index fb9e640..a265146 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -25,6 +25,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -87,7 +88,7 @@
         StateManager stateManager = mActivity.getStateManager();
         animated &= stateManager.shouldAnimateStateChange();
         stateManager.goToState(NORMAL, animated);
-        if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+        if (FeatureFlags.enableSplitContextually()) {
             mSplitSelectStateController.getSplitAnimationController()
                     .playPlaceholderDismissAnim(mActivity);
         }
@@ -232,7 +233,7 @@
 
     @Override
     protected boolean canLaunchFullscreenTask() {
-        if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+        if (FeatureFlags.enableSplitContextually()) {
             return !mSplitSelectStateController.isSplitSelectActive();
         } else {
             return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
@@ -255,7 +256,7 @@
         DesktopVisibilityController desktopVisibilityController = null;
         boolean showDesktopApps = false;
         GestureState.GestureEndTarget endTarget = null;
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             desktopVisibilityController = mActivity.getDesktopVisibilityController();
             endTarget = mCurrentGestureEndTarget;
             if (endTarget == GestureState.GestureEndTarget.LAST_TASK
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index e6f015e..7972999 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -56,7 +56,7 @@
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 import static com.android.quickstep.util.LogUtils.splitFailureMessage;
 import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
-import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET;
 import static com.android.quickstep.views.OverviewActionsView.FLAG_SINGLE_TASK;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_ACTIONS_IN_MENU;
@@ -1087,7 +1087,7 @@
                 mIPipAnimationListener);
         mOrientationState.initListeners();
         mTaskOverlayFactory.initListeners();
-        if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+        if (FeatureFlags.enableSplitContextually()) {
             mSplitSelectStateController.registerSplitListener(mSplitSelectionListener);
         }
     }
@@ -1108,7 +1108,7 @@
         mIPipAnimationListener.setActivityAndRecentsView(null, null);
         mOrientationState.destroyListeners();
         mTaskOverlayFactory.removeListeners();
-        if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+        if (FeatureFlags.enableSplitContextually()) {
             mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener);
         }
     }
@@ -1623,7 +1623,7 @@
         mMovingTaskView = null;
         runningTaskView.resetPersistentViewTransforms();
         int frontTaskIndex = 0;
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && mDesktopTaskView != null
+        if (isDesktopModeSupported() && mDesktopTaskView != null
                 && !runningTaskView.isDesktopTask()) {
             // If desktop mode is enabled, desktop task view is pinned at first position if present.
             // Move running task to position 1.
@@ -1763,7 +1763,7 @@
 
         if (!taskGroups.isEmpty()) {
             addView(mClearAllButton);
-            if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+            if (isDesktopModeSupported()) {
                 // Check if we have apps on the desktop
                 if (desktopTask != null && !desktopTask.tasks.isEmpty()) {
                     // If we are actively choosing apps for split, skip the desktop tile
@@ -2068,7 +2068,7 @@
                 mLastComputedGridSize);
         mSizeStrategy.calculateGridTaskSize(mActivity, mActivity.getDeviceProfile(),
                 mLastComputedGridTaskSize, mOrientationHandler);
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             mSizeStrategy.calculateDesktopTaskSize(mActivity, mActivity.getDeviceProfile(),
                     mLastComputedDesktopTaskSize);
         }
@@ -2101,11 +2101,15 @@
         }
 
         float accumulatedTranslationX = 0;
+        float translateXToMiddle = enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
+                ? mActivity.getDeviceProfile().widthPx / 2 - mLastComputedGridTaskSize.centerX()
+                : 0;
         for (int i = 0; i < taskCount; i++) {
             TaskView taskView = requireTaskViewAt(i);
             taskView.updateTaskSize();
             taskView.getPrimaryNonGridTranslationProperty().set(taskView, accumulatedTranslationX);
             taskView.getSecondaryNonGridTranslationProperty().set(taskView, 0f);
+            taskView.setNonGridPivotTranslationX(translateXToMiddle);
             // Compensate space caused by TaskView scaling.
             float widthDiff =
                     taskView.getLayoutParams().width * (1 - taskView.getNonGridScale());
@@ -2418,7 +2422,7 @@
             remoteTargetHandle.getTransformParams().setTargetSet(null);
             remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false);
         });
-        if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+        if (!FeatureFlags.enableSplitContextually()) {
             resetFromSplitSelectionState();
         }
 
@@ -2755,7 +2759,7 @@
     }
 
     private boolean hasDesktopTask(Task[] runningTasks) {
-        if (!DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (!isDesktopModeSupported()) {
             return false;
         }
         for (Task task : runningTasks) {
@@ -3307,7 +3311,7 @@
                         InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
             } else {
                 // If transition to split select was interrupted, clean up to prevent glitches
-                if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+                if (FeatureFlags.enableSplitContextually()) {
                     mSplitSelectStateController.resetState();
                 } else {
                     resetFromSplitSelectionState();
@@ -3935,7 +3939,7 @@
         mActionsView.updateSplitButtonHiddenFlags(FLAG_IS_NOT_TABLET,
                 !mActivity.getDeviceProfile().isTablet);
         mActionsView.updateSplitButtonDisabledFlags(FLAG_SINGLE_TASK, /*enable=*/ false);
-        if (DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
             mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
         }
@@ -4322,14 +4326,19 @@
 
             Utilities.getPivotsForScalingRectToRect(mTempRect, selectedTaskPosition,
                     mTempPointF);
-            setPivotX(mTempPointF.x);
-            setPivotY(mTempPointF.y);
         } else {
+            mTempRect.set(mLastComputedTaskSize);
+            // Only update pivot when it is tablet and not in grid yet, so the pivot is correct
+            // for non-current tasks when swiping up to overview
+            if (enableGridOnlyOverview() && mActivity.getDeviceProfile().isTablet
+                    && !mOverviewGridEnabled) {
+                mTempRect.offset(mActivity.getDeviceProfile().widthPx / 2 - mTempRect.centerX(), 0);
+            }
             getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
                     mActivity.getDeviceProfile(), mTempPointF);
-            setPivotX(mTempPointF.x);
-            setPivotY(mTempPointF.y);
         }
+        setPivotX(mTempPointF.x);
+        setPivotY(mTempPointF.y);
     }
 
     private void updatePageOffsets() {
@@ -4604,7 +4613,7 @@
         mSplitSelectStateController.setAnimateCurrentTaskDismissal(
                 true /*animateCurrentTaskDismissal*/);
         mSplitHiddenTaskViewIndex = indexOfChild(taskView);
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             updateDesktopTaskVisibility(false /* visible */);
         }
     }
@@ -4628,7 +4637,7 @@
         mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
                 splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
                 splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId);
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             updateDesktopTaskVisibility(false /* visible */);
         }
     }
@@ -4760,7 +4769,7 @@
         pendingAnimation.addEndListener(aBoolean -> {
             mSplitSelectStateController.launchSplitTasks(
                     aBoolean1 -> {
-                        if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+                        if (FeatureFlags.enableSplitContextually()) {
                             mSplitSelectStateController.resetState();
                         } else {
                             resetFromSplitSelectionState();
@@ -4789,7 +4798,7 @@
     @SuppressLint("WrongCall")
     protected void resetFromSplitSelectionState() {
         if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1 ||
-                FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+                FeatureFlags.enableSplitContextually()) {
             safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView());
             safeRemoveDragLayerView(mSecondFloatingTaskView);
             safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
@@ -4809,7 +4818,7 @@
         setTaskViewsPrimarySplitTranslation(0);
         setTaskViewsSecondarySplitTranslation(0);
 
-        if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+        if (!FeatureFlags.enableSplitContextually()) {
             // When flag is on, this method gets called from resetState() call below, let's avoid
             // infinite recursion today
             mSplitSelectStateController.resetState();
@@ -4834,7 +4843,7 @@
             mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
             mSplitHiddenTaskView = null;
         }
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+        if (isDesktopModeSupported()) {
             updateDesktopTaskVisibility(true /* visible */);
         }
     }
@@ -5209,7 +5218,7 @@
         }
 
         RemoteTargetGluer gluer;
-        if (DESKTOP_MODE_SUPPORTED && recentsAnimationTargets.hasDesktopTasks()) {
+        if (isDesktopModeSupported() && recentsAnimationTargets.hasDesktopTasks()) {
             gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets,
                     true /* forDesktop */);
             mRemoteTargetHandles = gluer.assignTargetsForDesktop(recentsAnimationTargets);
@@ -5382,7 +5391,7 @@
     }
 
     private int getFirstViewIndex() {
-        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && mDesktopTaskView != null) {
+        if (isDesktopModeSupported() && mDesktopTaskView != null) {
             // Desktop task is at position 0, that is the first view
             return 0;
         }
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index 4ca02e0..e8f06ee 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -99,7 +99,7 @@
     private void init() {
         mTextView = findViewById(R.id.split_instructions_text);
 
-        if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+        if (!FeatureFlags.enableSplitContextually()) {
             mTextView.setCompoundDrawables(null, null, null, null);
             return;
         }
@@ -138,7 +138,7 @@
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
-        if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+        if (!FeatureFlags.enableSplitContextually()) {
             return;
         }
 
@@ -150,7 +150,7 @@
 
     @Override
     public boolean performAccessibilityAction(int action, Bundle arguments) {
-        if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+        if (!FeatureFlags.enableSplitContextually()) {
             return super.performAccessibilityAction(action, arguments);
         }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index fec17b4..5093f22 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -36,6 +36,7 @@
 import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
 import static com.android.quickstep.TaskOverlayFactory.getEnabledShortcuts;
 import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
+import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -384,6 +385,7 @@
     // switch.
     private float mNonGridTranslationX;
     private float mNonGridTranslationY;
+    private float mNonGridPivotTranslationX;
     // Used when in SplitScreenSelectState
     private float mSplitSelectTranslationY;
     private float mSplitSelectTranslationX;
@@ -445,7 +447,7 @@
         mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
 
         boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
-                || DesktopTaskView.DESKTOP_MODE_SUPPORTED;
+                || isDesktopModeSupported();
         boolean cursorHoverStatesEnabled = enableCursorHoverStates();
 
         setWillNotDraw(!keyboardFocusHighlightEnabled && !cursorHoverStatesEnabled);
@@ -1284,8 +1286,8 @@
     }
 
     protected void resetPersistentViewTransforms() {
-        mNonGridTranslationX = mNonGridTranslationY =
-                mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f;
+        mNonGridTranslationX = mNonGridTranslationY = mGridTranslationX =
+                mGridTranslationY = mBoxTranslationY = mNonGridPivotTranslationX = 0f;
         resetViewTransforms();
     }
 
@@ -1487,6 +1489,14 @@
         applyTranslationX();
     }
 
+    /**
+     * Set translation X for non-grid pivot
+     */
+    public void setNonGridPivotTranslationX(float nonGridPivotTranslationX) {
+        mNonGridPivotTranslationX = nonGridPivotTranslationX;
+        applyTranslationX();
+    }
+
     public float getScrollAdjustment(boolean gridEnabled) {
         float scrollAdjustment = 0;
         if (gridEnabled) {
@@ -1529,7 +1539,8 @@
      * change according to a temporary state (e.g. task offset).
      */
     public float getPersistentTranslationX() {
-        return getNonGridTrans(mNonGridTranslationX) + getGridTrans(mGridTranslationX);
+        return getNonGridTrans(mNonGridTranslationX) + getGridTrans(mGridTranslationX)
+                + getNonGridTrans(mNonGridPivotTranslationX);
     }
 
     /**
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
index 2c16c15..15b1e53 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
@@ -55,7 +55,7 @@
     @Mock lateinit var taskbarOverlayController: TaskbarOverlayController
     @Mock lateinit var taskbarEduTooltipController: TaskbarEduTooltipController
     @Mock lateinit var keyboardQuickSwitchController: KeyboardQuickSwitchController
-    @Mock lateinit var taskbarPinningController: TaskbarDividerPopupController
+    @Mock lateinit var taskbarPinningController: TaskbarPinningController
     @Mock lateinit var optionalBubbleControllers: Optional<BubbleControllers>
 
     lateinit var taskbarControllers: TaskbarControllers
diff --git a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconAppChipMenuTest.java b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconAppChipMenuTest.java
index 15952c1..969da68 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconAppChipMenuTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconAppChipMenuTest.java
@@ -18,6 +18,7 @@
 import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 
+import org.junit.After;
 import org.junit.Before;
 
 /**
@@ -35,4 +36,11 @@
         executeOnLauncher(launcher -> InvariantDeviceProfile.INSTANCE.get(launcher).onConfigChanged(
                 launcher));
     }
+
+    @After
+    public void tearDown() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_OVERVIEW_ICON_MENU);
+        executeOnLauncher(launcher -> InvariantDeviceProfile.INSTANCE.get(launcher).onConfigChanged(
+                launcher));
+    }
 }
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 05a6452..1049314 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -29,7 +29,6 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.util.Log;
-import android.view.View;
 import android.window.OnBackInvokedDispatcher;
 
 import androidx.annotation.IntDef;
@@ -38,6 +37,7 @@
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.ViewCache;
 import com.android.launcher3.views.ActivityContext;
@@ -153,6 +153,18 @@
 
     private final ViewCache mViewCache = new ViewCache();
 
+    @Retention(SOURCE)
+    @IntDef({EVENT_STARTED, EVENT_RESUMED, EVENT_STOPPED, EVENT_DESTROYED})
+    public @interface ActivityEvent { }
+    public static final int EVENT_STARTED = 0;
+    public static final int EVENT_RESUMED = 1;
+    public static final int EVENT_STOPPED = 2;
+    public static final int EVENT_DESTROYED = 3;
+
+    // Callback array that corresponds to events defined in @ActivityEvent
+    private final RunnableList[] mEventCallbacks =
+            {new RunnableList(), new RunnableList(), new RunnableList(), new RunnableList()};
+
     @Override
     public ViewCache getViewCache() {
         return mViewCache;
@@ -205,12 +217,14 @@
     protected void onStart() {
         addActivityFlags(ACTIVITY_STATE_STARTED);
         super.onStart();
+        mEventCallbacks[EVENT_STARTED].executeAllAndClear();
     }
 
     @Override
     protected void onResume() {
         setResumed();
         super.onResume();
+        mEventCallbacks[EVENT_RESUMED].executeAllAndClear();
     }
 
     @Override
@@ -232,6 +246,8 @@
         removeActivityFlags(ACTIVITY_STATE_STARTED | ACTIVITY_STATE_USER_ACTIVE);
         mForceInvisible = 0;
         super.onStop();
+        mEventCallbacks[EVENT_STOPPED].executeAllAndClear();
+
 
         // Reset the overridden sysui flags used for the task-swipe launch animation, this is a
         // catch all for if we do not get resumed (and therefore not paused below)
@@ -239,6 +255,12 @@
     }
 
     @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mEventCallbacks[EVENT_DESTROYED].executeAllAndClear();
+    }
+
+    @Override
     protected void onPause() {
         setPaused();
         super.onPause();
@@ -258,7 +280,6 @@
         } else {
             removeActivityFlags(ACTIVITY_STATE_WINDOW_FOCUSED);
         }
-
     }
 
     protected void registerBackDispatcher() {
@@ -364,9 +385,15 @@
     }
 
     /**
-     * Attempts to clear accessibility focus on {@param view}.
+     * Adds a callback for the provided activity event
      */
-    public void tryClearAccessibilityFocus(View view) {
+    public void addEventCallback(@ActivityEvent int event, Runnable callback) {
+        mEventCallbacks[event].add(callback);
+    }
+
+    /** Removes a previously added callback */
+    public void removeEventCallback(@ActivityEvent int event, Runnable callback) {
+        mEventCallbacks[event].remove(callback);
     }
 
     public interface MultiWindowModeChangedListener {
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 808cf70..f8ed4df 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.OnColorHintListener;
-import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.WallpaperColorHints;
@@ -51,8 +50,6 @@
 public abstract class BaseDraggingActivity extends BaseActivity
         implements OnColorHintListener, DisplayInfoChangeListener {
 
-    private static final String TAG = "BaseDraggingActivity";
-
     // When starting an action mode, setting this tag will cause the action mode to be cancelled
     // automatically when user interacts with the launcher.
     public static final Object AUTO_CANCEL_ACTION_MODE = new Object();
@@ -60,8 +57,6 @@
     private ActionMode mCurrentActionMode;
     protected boolean mIsSafeModeEnabled;
 
-    private Runnable mOnStartCallback;
-    private final RunnableList mOnResumeCallbacks = new RunnableList();
     private int mThemeRes = R.style.AppTheme;
 
     @Override
@@ -81,16 +76,6 @@
         }
     }
 
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mOnResumeCallbacks.executeAllAndClear();
-    }
-
-    public void addOnResumeCallback(Runnable callback) {
-        mOnResumeCallbacks.add(callback);
-    }
-
     @MainThread
     @Override
     public void onColorHintsChanged(int colorHints) {
@@ -146,42 +131,24 @@
     @NonNull
     public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
         ActivityOptionsWrapper wrapper = super.getActivityLaunchOptions(v, item);
-        addOnResumeCallback(wrapper.onEndCallback::executeAllAndDestroy);
+        addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
         return wrapper;
     }
 
     @Override
     public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
         ActivityOptionsWrapper wrapper = super.makeDefaultActivityOptions(splashScreenStyle);
-        addOnResumeCallback(wrapper.onEndCallback::executeAllAndDestroy);
+        addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
         return wrapper;
     }
 
     @Override
-    protected void onStart() {
-        super.onStart();
-
-        if (mOnStartCallback != null) {
-            mOnStartCallback.run();
-            mOnStartCallback = null;
-        }
-    }
-
-    @Override
     protected void onDestroy() {
         super.onDestroy();
         DisplayController.INSTANCE.get(this).removeChangeListener(this);
         WallpaperColorHints.get(this).unregisterOnColorsChangedListener(this);
     }
 
-    public void runOnceOnStart(Runnable action) {
-        mOnStartCallback = action;
-    }
-
-    public void clearRunOnceOnStartCallback() {
-        mOnStartCallback = null;
-    }
-
     protected void onDeviceProfileInitiated() {
         if (mDeviceProfile.isVerticalBarLayout()) {
             mDeviceProfile.updateIsSeascape(this);
diff --git a/src/com/android/launcher3/DropTargetHandler.kt b/src/com/android/launcher3/DropTargetHandler.kt
index 6560e16..78f2862 100644
--- a/src/com/android/launcher3/DropTargetHandler.kt
+++ b/src/com/android/launcher3/DropTargetHandler.kt
@@ -2,6 +2,7 @@
 
 import android.content.ComponentName
 import android.view.View
+import com.android.launcher3.BaseDraggingActivity.EVENT_RESUMED
 import com.android.launcher3.DropTarget.DragObject
 import com.android.launcher3.SecondaryDropTarget.DeferredOnComplete
 import com.android.launcher3.dragndrop.DragLayer
@@ -32,7 +33,7 @@
                 if (d.dragSource is SecondaryDropTarget.DeferredOnComplete) {
                     target?.let {
                         deferred.mPackageName = it.packageName
-                        mLauncher.addOnResumeCallback { deferred.onLauncherResume() }
+                        mLauncher.addEventCallback(EVENT_RESUMED) { deferred.onLauncherResume() }
                     }
                         ?: deferred.sendFailure()
                 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c0520c1..edff48b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -237,6 +237,7 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.shared.LauncherOverlayManager;
 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
+import com.android.wm.shell.Flags;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -330,6 +331,7 @@
     private static final FloatProperty<Hotseat> HOTSEAT_WIDGET_SCALE =
             HOTSEAT_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WIDGET_TRANSITION);
 
+    private static final boolean ENABLE_DESKTOP_WINDOWING = Flags.enableDesktopWindowing();
     private static final boolean DESKTOP_MODE_SUPPORTED =
             "1".equals(Utilities.getSystemProperty("persist.wm.debug.desktop_mode_2", "0"));
 
@@ -688,7 +690,7 @@
 
     private void switchOverlay(Supplier<LauncherOverlayManager> overlaySupplier) {
         if (mOverlayManager != null) {
-            mOverlayManager.onActivityDestroyed(this);
+            mOverlayManager.onActivityDestroyed();
         }
         mOverlayManager = overlaySupplier.get();
         if (getRootView().isAttachedToWindow()) {
@@ -1029,7 +1031,7 @@
         if (mDeferOverlayCallbacks) {
             checkIfOverlayStillDeferred();
         } else {
-            mOverlayManager.onActivityStopped(this);
+            mOverlayManager.onActivityStopped();
         }
         hideKeyboard();
         logStopAndResume(false /* isResume */);
@@ -1043,7 +1045,7 @@
         TraceHelper.INSTANCE.beginSection(ON_START_EVT);
         super.onStart();
         if (!mDeferOverlayCallbacks) {
-            mOverlayManager.onActivityStarted(this);
+            mOverlayManager.onActivityStarted();
         }
 
         mAppWidgetHolder.setActivityStarted(true);
@@ -1112,15 +1114,15 @@
 
         // Move the client to the correct state. Calling the same method twice is no-op.
         if (isStarted()) {
-            mOverlayManager.onActivityStarted(this);
+            mOverlayManager.onActivityStarted();
         }
         if (hasBeenResumed()) {
-            mOverlayManager.onActivityResumed(this);
+            mOverlayManager.onActivityResumed();
         } else {
-            mOverlayManager.onActivityPaused(this);
+            mOverlayManager.onActivityPaused();
         }
         if (!isStarted()) {
-            mOverlayManager.onActivityStopped(this);
+            mOverlayManager.onActivityStopped();
         }
     }
 
@@ -1220,7 +1222,7 @@
         if (mDeferOverlayCallbacks) {
             scheduleDeferredCheck();
         } else {
-            mOverlayManager.onActivityResumed(this);
+            mOverlayManager.onActivityResumed();
         }
 
         DragView.removeAllViews(this);
@@ -1238,7 +1240,7 @@
         mDropTargetBar.animateToVisibility(false);
 
         if (!mDeferOverlayCallbacks) {
-            mOverlayManager.onActivityPaused(this);
+            mOverlayManager.onActivityPaused();
         }
         mAppWidgetHolder.setActivityResumed(false);
     }
@@ -1578,7 +1580,7 @@
                 }
             }
 
-            if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+            if (FeatureFlags.enableSplitContextually()) {
                 handleSplitAnimationGoingToHome();
             }
             mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
@@ -1683,7 +1685,6 @@
         }
 
         super.onSaveInstanceState(outState);
-        mOverlayManager.onActivitySaveInstanceState(this, outState);
     }
 
     @Override
@@ -1709,7 +1710,7 @@
         clearPendingBinds();
         LauncherAppState.getIDP(this).removeOnChangeListener(this);
 
-        mOverlayManager.onActivityDestroyed(this);
+        mOverlayManager.onActivityDestroyed();
     }
 
     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1742,7 +1743,7 @@
         try {
             super.startIntentSenderForResult(intent, requestCode,
                     fillInIntent, flagsMask, flagsValues, extraFlags, options);
-        } catch (IntentSender.SendIntentException e) {
+        } catch (Exception e) {
             throw new ActivityNotFoundException();
         }
     }
@@ -2004,7 +2005,7 @@
             // Workaround an issue where the WM launch animation is clobbered when finishing the
             // recents animation into launcher. Defer launching the activity until Launcher is
             // next resumed.
-            addOnResumeCallback(() -> {
+            addEventCallback(EVENT_RESUMED, () -> {
                 RunnableList actualResult = startActivitySafely(v, intent, item);
                 if (actualResult != null) {
                     actualResult.add(result::executeAllAndDestroy);
@@ -3044,7 +3045,8 @@
     }
 
     private void updateDisallowBack() {
-        if (DESKTOP_MODE_SUPPORTED) {
+        // TODO(b/304778354): remove sysprop once desktop aconfig flag supports dynamic overriding
+        if (ENABLE_DESKTOP_WINDOWING || DESKTOP_MODE_SUPPORTED) {
             // Do not disable back in launcher when prototype behavior is enabled
             return;
         }
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index c6a9283..4f8caab 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -20,11 +20,12 @@
 import android.content.SharedPreferences
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener
 import android.util.Log
-import android.view.ViewConfiguration
 import androidx.annotation.VisibleForTesting
 import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
 import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
 import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
+import com.android.launcher3.config.FeatureFlags.LPNH_SLOP_PERCENTAGE
+import com.android.launcher3.config.FeatureFlags.LPNH_TIMEOUT_MS
 import com.android.launcher3.model.DeviceGridState
 import com.android.launcher3.pm.InstallSessionHelper
 import com.android.launcher3.provider.RestoreDbTask
@@ -310,16 +311,16 @@
             )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
-                nonRestorableItem(
-                        "pref_long_press_nav_handle_slop_multiplier",
-                        100,
+            nonRestorableItem(
+                "pref_long_press_nav_handle_slop_percentage",
+                        LPNH_SLOP_PERCENTAGE.get(),
                         EncryptionType.MOVE_TO_DEVICE_PROTECTED
-                )
+            )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
                 nonRestorableItem(
                         "pref_long_press_nav_handle_timeout_ms",
-                        ViewConfiguration.getLongPressTimeout(),
+                        LPNH_TIMEOUT_MS.get(),
                         EncryptionType.MOVE_TO_DEVICE_PROTECTED
                 )
         @JvmField
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 9c0a508..4e7ec0b 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -21,8 +21,11 @@
 import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
 import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
 import static com.android.launcher3.uioverrides.flags.FlagsFactory.getDebugFlag;
+import static com.android.launcher3.uioverrides.flags.FlagsFactory.getIntFlag;
 import static com.android.launcher3.uioverrides.flags.FlagsFactory.getReleaseFlag;
 
+import android.view.ViewConfiguration;
+
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.BuildConfig;
@@ -113,9 +116,17 @@
                     "Allow entering All Apps from Overview (e.g. long swipe up from app)");
 
     public static final BooleanFlag CUSTOM_LPNH_THRESHOLDS =
-            getDebugFlag(301680992, "CUSTOM_LPNH_THRESHOLDS", DISABLED,
+            getReleaseFlag(301680992, "CUSTOM_LPNH_THRESHOLDS", DISABLED,
                     "Add dev options to customize the LPNH trigger slop and milliseconds");
 
+    public static final IntFlag LPNH_SLOP_PERCENTAGE =
+            getIntFlag(301680992, "LPNH_SLOP_PERCENTAGE", 100,
+                    "Controls touch slop percentage for lpnh");
+
+    public static final IntFlag LPNH_TIMEOUT_MS =
+            getIntFlag(301680992, "LPNH_TIMEOUT_MS", ViewConfiguration.getLongPressTimeout(),
+                    "Controls lpnh timeout in milliseconds");
+
     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");
@@ -202,9 +213,13 @@
     public static final BooleanFlag ENABLE_TRANSIENT_TASKBAR = getDebugFlag(270395798,
             "ENABLE_TRANSIENT_TASKBAR", ENABLED, "Enables transient taskbar.");
 
+    // Aconfig migration complete for ENABLE_TASKBAR_NO_RECREATION.
     public static final BooleanFlag ENABLE_TASKBAR_NO_RECREATION = getDebugFlag(299193589,
             "ENABLE_TASKBAR_NO_RECREATION", DISABLED,
             "Enables taskbar with no recreation from lifecycle changes of TaskbarActivityContext.");
+    public static boolean enableTaskbarNoRecreate() {
+        return ENABLE_TASKBAR_NO_RECREATION.get() || Flags.enableTaskbarNoRecreate();
+    }
 
     // TODO(Block 16): Clean up flags
     // When enabled the promise icon is visible in all apps while installation an app.
@@ -301,10 +316,15 @@
             "ENABLE_DYNAMIC_TASKBAR_THRESHOLDS", ENABLED,
             "Enables taskbar thresholds that scale based on screen size.");
 
+    // Aconfig migration complete for ENABLE_HOME_TRANSITION_LISTENER.
     public static final BooleanFlag ENABLE_HOME_TRANSITION_LISTENER = getDebugFlag(306053414,
-            "ENABLE_HOME_TRANSITION_LISTENER", DISABLED,
+            "ENABLE_HOME_TRANSITION_LISTENER", TEAMFOOD,
             "Enables launcher to listen to all transitions that include home activity.");
 
+    public static boolean enableHomeTransitionListener() {
+        return ENABLE_HOME_TRANSITION_LISTENER.get() || Flags.enableHomeTransitionListener();
+    }
+
     // TODO(Block 21): Clean up flags
     public static final BooleanFlag ENABLE_APP_ICON_FOR_INLINE_SHORTCUTS = getDebugFlag(270395087,
             "ENABLE_APP_ICON_IN_INLINE_SHORTCUTS", DISABLED, "Show app icon for inline shortcut");
@@ -379,6 +399,10 @@
     public static final BooleanFlag ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE = getDebugFlag(
             270393453, "ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE", DISABLED,
             "Enable initiating split screen from workspace to workspace.");
+    public static boolean enableSplitContextually() {
+        return ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get() ||
+                com.android.wm.shell.Flags.enableSplitContextual();
+    }
 
     public static final BooleanFlag ENABLE_TRACKPAD_GESTURE = getDebugFlag(271010401,
             "ENABLE_TRACKPAD_GESTURE", ENABLED, "Enables trackpad gesture.");
diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java
index 8167b97..1360510 100644
--- a/src/com/android/launcher3/model/DatabaseHelper.java
+++ b/src/com/android/launcher3/model/DatabaseHelper.java
@@ -269,7 +269,6 @@
                                     Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP,
                                     Favorites.CELLY, 0), null);
                 }
-                return;
             }
             case 31: {
                 LauncherDbUtils.migrateLegacyShortcuts(mContext, db);
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index a09e5a4..0c322cc 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -138,8 +138,9 @@
         // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
         if (launcher.getDragController().isDragging()) return false;
         // Return early if user is in the middle of selecting split-screen apps
-        if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get() &&
-                launcher.isSplitSelectionEnabled()) return false;
+        if (FeatureFlags.enableSplitContextually() && launcher.isSplitSelectionEnabled()) {
+            return false;
+        }
 
         return true;
     }
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 1232069..5b6c9e0 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -206,8 +206,7 @@
                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                 mLauncher.getStatsLogManager().logger().log(LAUNCHER_WORKSPACE_LONGPRESS);
                 mLauncher.showDefaultOptions(mTouchDownPoint.x, mTouchDownPoint.y);
-                if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get() &&
-                        mLauncher.isSplitSelectionEnabled()) {
+                if (FeatureFlags.enableSplitContextually() && mLauncher.isSplitSelectionEnabled()) {
                     mLauncher.dismissSplitSelection();
                 }
             } else {
diff --git a/src/com/android/launcher3/util/MultiTranslateDelegate.java b/src/com/android/launcher3/util/MultiTranslateDelegate.java
index 6f6392f..84ef445 100644
--- a/src/com/android/launcher3/util/MultiTranslateDelegate.java
+++ b/src/com/android/launcher3/util/MultiTranslateDelegate.java
@@ -36,6 +36,7 @@
     // Specific for items in taskbar (icons, folders, qsb)
     public static final int INDEX_TASKBAR_ALIGNMENT_ANIM = 3;
     public static final int INDEX_TASKBAR_REVEAL_ANIM = 4;
+    public static final int INDEX_TASKBAR_PINNING_ANIM = 5;
 
     // Affect all items inside of a MultipageCellLayout
     public static final int INDEX_CELLAYOUT_MULTIPAGE_SPACING = 3;
@@ -46,7 +47,7 @@
     // Specific for hotseat items when adjusting for bubbles
     public static final int INDEX_BUBBLE_ADJUSTMENT_ANIM = 3;
 
-    public static final int COUNT = 5;
+    public static final int COUNT = 6;
 
     private final MultiPropertyFactory<View> mTranslationX;
     private final MultiPropertyFactory<View> mTranslationY;
diff --git a/src/com/android/launcher3/util/RunnableList.java b/src/com/android/launcher3/util/RunnableList.java
index 644537b..f6e0c57 100644
--- a/src/com/android/launcher3/util/RunnableList.java
+++ b/src/com/android/launcher3/util/RunnableList.java
@@ -25,9 +25,7 @@
     private ArrayList<Runnable> mList = null;
     private boolean mDestroyed = false;
 
-    /**
-     * Ads a runnable to this list
-     */
+    /** Adds a runnable to this list */
     public void add(Runnable runnable) {
         if (runnable == null) {
             return;
@@ -42,6 +40,13 @@
         mList.add(runnable);
     }
 
+    /** Removes a previously added runnable */
+    public void remove(Runnable runnable) {
+        if (mList != null) {
+            mList.remove(runnable);
+        }
+    }
+
     /**
      * Destroys the list, executing any pending callbacks. All new callbacks are
      * immediately executed
diff --git a/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
index 6b27503..54cc0bc 100644
--- a/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
+++ b/src_plugins/com/android/systemui/plugins/shared/LauncherOverlayManager.java
@@ -15,16 +15,12 @@
  */
 package com.android.systemui.plugins.shared;
 
-import android.app.Activity;
-import android.app.Application;
-import android.os.Bundle;
-
 import java.io.PrintWriter;
 
 /**
  * Interface to control the overlay on Launcher
  */
-public interface LauncherOverlayManager extends Application.ActivityLifecycleCallbacks {
+public interface LauncherOverlayManager {
 
     default void onDeviceProvideChanged() { }
 
@@ -41,26 +37,15 @@
 
     default void hideOverlay(int duration) { }
 
-    @Override
-    default void onActivityCreated(Activity activity, Bundle bundle) { }
+    default void onActivityStarted() { }
 
-    @Override
-    default void onActivityStarted(Activity activity) { }
+    default void onActivityResumed() { }
 
-    @Override
-    default void onActivityResumed(Activity activity) { }
+    default void onActivityPaused() { }
 
-    @Override
-    default void onActivityPaused(Activity activity) { }
+    default void onActivityStopped() { }
 
-    @Override
-    default void onActivityStopped(Activity activity) { }
-
-    @Override
-    default void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
-
-    @Override
-    default void onActivityDestroyed(Activity activity) { }
+    default void onActivityDestroyed() { }
 
     interface LauncherOverlay {
 
diff --git a/tests/src/com/android/launcher3/DeleteDropTargetTest.kt b/tests/src/com/android/launcher3/DeleteDropTargetTest.kt
index bcfb90b..46e66e4 100644
--- a/tests/src/com/android/launcher3/DeleteDropTargetTest.kt
+++ b/tests/src/com/android/launcher3/DeleteDropTargetTest.kt
@@ -32,9 +32,9 @@
         buttonDropTarget.setTextMultiLine(false)
 
         // No space for text
-        assertThat(buttonDropTarget.isTextClippedVertically(30)).isTrue()
+        assertThat(buttonDropTarget.isTextClippedVertically(1)).isTrue()
 
         // A lot of space for text so the text should not be clipped
-        assertThat(buttonDropTarget.isTextClippedVertically(100)).isFalse()
+        assertThat(buttonDropTarget.isTextClippedVertically(1000)).isFalse()
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index aa5c770..a85b6bd 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -366,8 +366,10 @@
         }
         int focusedTaskHeight = mLauncher.getFocusedTaskHeightForTablet();
         for (UiObject2 task : taskViews) {
-            if (task.getVisibleBounds().height() == focusedTaskHeight) {
-                return new OverviewTask(mLauncher, task, this);
+            OverviewTask overviewTask = new OverviewTask(mLauncher, task, this);
+
+            if (overviewTask.getVisibleHeight() == focusedTaskHeight) {
+                return overviewTask;
             }
         }
         return null;
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 95a4802..06fac48 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -36,6 +36,8 @@
  */
 public final class OverviewTask {
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+    private static final String TASK_SNAPSHOT_1 = "snapshot";
+    private static final String TASK_SNAPSHOT_2 = "bottomright_snapshot";
 
     static final Pattern TASK_START_EVENT = Pattern.compile("startActivityFromRecentsAsync");
     static final Pattern SPLIT_SELECT_EVENT = Pattern.compile("enterSplitSelect");
@@ -55,14 +57,64 @@
         mOverview.verifyActiveContainer();
     }
 
+    /**
+     * Returns the height of the visible task, or the combined height of two tasks in split with a
+     * divider between.
+     */
     int getVisibleHeight() {
+        if (isTaskSplit()) {
+            return getCombinedSplitTaskHeight();
+        }
+
         return mTask.getVisibleBounds().height();
     }
 
+    /**
+     * Calculates the visible height for split tasks, containing 2 snapshot tiles and a divider.
+     */
+    private int getCombinedSplitTaskHeight() {
+        UiObject2 taskSnapshot1 =
+                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_1);
+        UiObject2 taskSnapshot2 =
+                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_2);
+
+        int top = Math.min(
+                taskSnapshot1.getVisibleBounds().top, taskSnapshot2.getVisibleBounds().top);
+        int bottom = Math.max(
+                taskSnapshot1.getVisibleBounds().bottom, taskSnapshot2.getVisibleBounds().bottom);
+
+        return bottom - top;
+    }
+
+    /**
+     * Returns the width of the visible task, or the combined width of two tasks in split with a
+     * divider between.
+     */
     int getVisibleWidth() {
+        if (isTaskSplit()) {
+            return getCombinedSplitTaskWidth();
+        }
+
         return mTask.getVisibleBounds().width();
     }
 
+    /**
+     * Calculates the visible width for split tasks, containing 2 snapshot tiles and a divider.
+     */
+    private int getCombinedSplitTaskWidth() {
+        UiObject2 taskSnapshot1 =
+                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_1);
+        UiObject2 taskSnapshot2 =
+                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_2);
+
+        int left = Math.min(
+                taskSnapshot1.getVisibleBounds().left, taskSnapshot2.getVisibleBounds().left);
+        int right = Math.max(
+                taskSnapshot1.getVisibleBounds().right, taskSnapshot2.getVisibleBounds().right);
+
+        return right - left;
+    }
+
     int getTaskCenterX() {
         return mTask.getVisibleCenter().x;
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Qsb.java b/tests/tapl/com/android/launcher3/tapl/Qsb.java
index 5ca80a3..fe2a63d 100644
--- a/tests/tapl/com/android/launcher3/tapl/Qsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/Qsb.java
@@ -22,6 +22,8 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import java.util.regex.Pattern;
+
 /**
  * Operations on qsb from either Home screen or AllApp screen.
  */
@@ -30,7 +32,8 @@
     private static final String ASSISTANT_APP_PACKAGE = "com.google.android.googlequicksearchbox";
     private static final String ASSISTANT_ICON_RES_ID = "mic_icon";
     private static final String LENS_ICON_RES_ID = "lens_icon";
-    private static final String LENS_APP_TEXT_RES_ID = "lens_camera_cutout_text";
+    private static final Pattern LENS_APP_RES_PATTERN = Pattern.compile(
+            ASSISTANT_APP_PACKAGE + ":id/lens.*");
     protected final LauncherInstrumentation mLauncher;
     private final UiObject2 mContainer;
     private final String mQsbResName;
@@ -96,8 +99,8 @@
             try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
                 // Package name is not enough to check if the app is launched, because many
                 // elements are having googlequicksearchbox as package name. So it checks if the
-                // corresponding text resource is displayed
-                BySelector selector = By.res(ASSISTANT_APP_PACKAGE, LENS_APP_TEXT_RES_ID);
+                // corresponding app resource is displayed
+                BySelector selector = By.res(LENS_APP_RES_PATTERN);
                 mLauncher.assertTrue(
                         "Lens app didn't start: (" + selector + ")",
                         mLauncher.getDevice().wait(Until.hasObject(selector),