Merge "Convert TaskMenuView.java to Kotlin" into main
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index fe57da1..46f0e41 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -131,13 +131,11 @@
             android:writePermission="${applicationId}.permission.WRITE_SETTINGS"
             android:readPermission="${applicationId}.permission.READ_SETTINGS" />
 
-        <!--
-        The content provider for exposing various launcher grid options.
-        TODO: Add proper permissions
-        -->
+        <!-- The content provider for exposing various launcher grid options. -->
         <provider
             android:name="com.android.launcher3.graphics.LauncherCustomizationProvider"
             android:authorities="${applicationId}.grid_control"
+            android:permission="android.permission.BIND_WALLPAPER"
             android:exported="true" />
 
         <!--
diff --git a/quickstep/res/layout/activity_allset.xml b/quickstep/res/layout/activity_allset.xml
index 625d9b3..3d68dfb 100644
--- a/quickstep/res/layout/activity_allset.xml
+++ b/quickstep/res/layout/activity_allset.xml
@@ -29,7 +29,6 @@
         android:layout_height="match_parent"
         android:gravity="center"
         android:scaleType="centerCrop"
-        app:lottie_autoPlay="true"
         app:lottie_loop="true"
 
         app:layout_constraintTop_toTopOf="parent"
@@ -49,11 +48,10 @@
         app:layout_constraintEnd_toEndOf="parent">
 
         <androidx.constraintlayout.widget.ConstraintLayout
-            android:id="@+id/text_content_view"
+            android:id="@+id/content"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/allset_page_margin_horizontal"
-            android:layout_marginEnd="@dimen/allset_page_margin_horizontal"
+            android:paddingHorizontal="@dimen/allset_page_padding_horizontal"
             android:layoutDirection="locale"
             android:textDirection="locale"
             android:forceHasOverlappingRendering="false"
diff --git a/quickstep/res/values-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml
index 0052a73..cf7ba00 100644
--- a/quickstep/res/values-sw600dp-land/dimens.xml
+++ b/quickstep/res/values-sw600dp-land/dimens.xml
@@ -16,7 +16,7 @@
 -->
 <resources>
     <!-- All Set page -->
-    <dimen name="allset_page_margin_horizontal">48dp</dimen>
+    <dimen name="allset_page_padding_horizontal">48dp</dimen>
 
     <!-- Gesture Tutorial menu page -->
     <dimen name="gesture_tutorial_menu_padding_horizontal">48dp</dimen>
diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml
index 4996582..3e72651 100644
--- a/quickstep/res/values-sw600dp/dimens.xml
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -37,7 +37,7 @@
     <dimen name="overview_actions_top_margin">24dp</dimen>
 
     <!-- All Set page -->
-    <dimen name="allset_page_margin_horizontal">120dp</dimen>
+    <dimen name="allset_page_padding_horizontal">120dp</dimen>
     <dimen name="allset_page_allset_text_size">38sp</dimen>
     <dimen name="allset_page_swipe_up_text_size">15sp</dimen>
 </resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 05f0695..52ebdae 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -277,7 +277,7 @@
     <dimen name="gesture_tutorial_taskbar_margin_bottom">24dp</dimen>
 
     <!-- All Set page -->
-    <dimen name="allset_page_margin_horizontal">40dp</dimen>
+    <dimen name="allset_page_padding_horizontal">40dp</dimen>
     <dimen name="allset_page_allset_text_size">36sp</dimen>
     <dimen name="allset_page_swipe_up_text_size">14sp</dimen>
 
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 67feb6a..84ae0fe 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -56,7 +56,6 @@
 import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE;
 import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
@@ -79,7 +78,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
-import android.database.ContentObserver;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
@@ -94,7 +92,6 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.provider.Settings.Global;
 import android.util.Pair;
 import android.util.Size;
 import android.view.CrossWindowBlurListeners;
@@ -249,16 +246,6 @@
     // {@link TaskRestartedDuringLaunchListener}, and remove them on activity destroy.
     private final List<TaskRestartedDuringLaunchListener> mRegisteredTaskStackChangeListener =
             new ArrayList<>();
-    private final ContentObserver mAnimationRemovalObserver = new ContentObserver(
-            ORDERED_BG_EXECUTOR.getHandler()) {
-        @Override
-        public void onChange(boolean selfChange) {
-            mAreAnimationsEnabled = Global.getFloat(mLauncher.getContentResolver(),
-                    Global.ANIMATOR_DURATION_SCALE, 1f) > 0
-                    || Global.getFloat(mLauncher.getContentResolver(),
-                    Global.TRANSITION_ANIMATION_SCALE, 1f) > 0;
-        }
-    };
 
     private DeviceProfile mDeviceProfile;
 
@@ -287,7 +274,6 @@
     // Pairs of window starting type and starting window background color for starting tasks
     // Will never be larger than MAX_NUM_TASKS
     private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams;
-    private boolean mAreAnimationsEnabled = true;
 
     private final Interpolator mOpeningXInterpolator;
     private final Interpolator mOpeningInterpolator;
@@ -298,7 +284,6 @@
         mHandler = new Handler(Looper.getMainLooper());
         mDeviceProfile = mLauncher.getDeviceProfile();
         mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
-        checkAndMonitorIfAnimationsAreEnabled();
 
         Resources res = mLauncher.getResources();
         mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
@@ -1220,8 +1205,6 @@
         unregisterRemoteTransitions();
         mLauncher.removeOnDeviceProfileChangeListener(this);
         SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
-        ORDERED_BG_EXECUTOR.execute(() -> mLauncher.getContentResolver()
-                .unregisterContentObserver(mAnimationRemovalObserver));
         if (BuildConfig.IS_STUDIO_BUILD && !mRegisteredTaskStackChangeListener.isEmpty()) {
             throw new IllegalStateException("Failed to run onEndCallback created from"
                     + " getActivityLaunchOptions()");
@@ -1275,17 +1258,6 @@
         }
     }
 
-    private void checkAndMonitorIfAnimationsAreEnabled() {
-        ORDERED_BG_EXECUTOR.execute(() -> {
-            mAnimationRemovalObserver.onChange(true);
-            mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
-                    Global.ANIMATOR_DURATION_SCALE), false, mAnimationRemovalObserver);
-            mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
-                    Global.TRANSITION_ANIMATION_SCALE), false, mAnimationRemovalObserver);
-
-        });
-    }
-
     private boolean launcherIsATargetWithMode(RemoteAnimationTarget[] targets, int mode) {
         for (RemoteAnimationTarget target : targets) {
             if (target.mode == mode && target.taskInfo != null
@@ -1421,7 +1393,8 @@
                     (LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
                     mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
                     isTransluscent, fallbackBackgroundColor);
-        } else if (launcherView != null && mAreAnimationsEnabled) {
+        } else if (launcherView != null && !RemoveAnimationSettingsTracker.INSTANCE.get(
+                mLauncher).isRemoveAnimationEnabled()) {
             floatingIconView = getFloatingIconView(mLauncher, launcherView, null,
                     mLauncher.getTaskbarUIController() == null
                             ? null
@@ -1811,8 +1784,8 @@
     }
 
     /** Get animation duration for taskbar for going to home. */
-    public static int getTaskbarToHomeDuration(boolean isPinnedTaskbar) {
-        return getTaskbarToHomeDuration(false, isPinnedTaskbar);
+    public static int getTaskbarToHomeDuration(boolean isPinnedTaskbarAndNotInDesktopMode) {
+        return getTaskbarToHomeDuration(false, isPinnedTaskbarAndNotInDesktopMode);
     }
 
     /**
@@ -1821,8 +1794,8 @@
      * @param shouldOverrideToFastAnimation should overwrite scaling reveal home animation duration
      */
     public static int getTaskbarToHomeDuration(boolean shouldOverrideToFastAnimation,
-            boolean isPinnedTaskbar) {
-        if (isPinnedTaskbar) {
+            boolean isPinnedTaskbarAndNotInDesktopMode) {
+        if (isPinnedTaskbarAndNotInDesktopMode) {
             return PINNED_TASKBAR_TRANSITION_DURATION;
         } else if (enableScalingRevealHomeAnimation() && !shouldOverrideToFastAnimation) {
             return TASKBAR_TO_HOME_DURATION_SLOW;
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
index 138f40a..eb24df1 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
@@ -32,7 +32,8 @@
 import com.android.launcher3.uioverrides.QuickstepLauncher
 import com.android.launcher3.util.DaggerSingletonObject
 import com.android.launcher3.util.DaggerSingletonTracker
-import com.android.launcher3.util.Executors
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
 import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener
 import com.android.quickstep.GestureState.GestureEndTarget
 import com.android.quickstep.SystemUiProxy
@@ -87,9 +88,22 @@
     private val desktopVisibilityListeners: MutableSet<DesktopVisibilityListener> = HashSet()
     private val taskbarDesktopModeListeners: MutableSet<TaskbarDesktopModeListener> = HashSet()
 
+    // This simply indicates that user is currently in desktop mode or not.
+    var isInDesktopMode = false
+        private set
+
+    // to track if any pending notification to be done.
+    var isNotifyingDesktopVisibilityPending = false
+
+    // to let launcher hold off on notifying desktop visibility listeners.
+    var launcherAnimationRunning = false
+
     // TODO: b/394387739 - Deprecate this and replace it with something that tracks the count per
     //  desk.
-    /** Number of visible desktop windows in desktop mode. */
+    /**
+     * Number of visible desktop windows in desktop mode. This can be > 0 when user goes to overview
+     * from desktop window mode.
+     */
     var visibleDesktopTasksCount: Int = 0
         /**
          * Sets the number of desktop windows that are visible and updates launcher visibility based
@@ -107,13 +121,27 @@
             }
 
             if (visibleTasksCount != field) {
+                if (visibleDesktopTasksCount == 0 && visibleTasksCount == 1) {
+                    isInDesktopMode = true
+                }
+                if (visibleDesktopTasksCount == 1 && visibleTasksCount == 0) {
+                    isInDesktopMode = false
+                }
                 val wasVisible = field > 0
                 val isVisible = visibleTasksCount > 0
                 val wereDesktopTasksVisibleBefore = areDesktopTasksVisibleAndNotInOverview()
                 field = visibleTasksCount
                 val areDesktopTasksVisibleNow = areDesktopTasksVisibleAndNotInOverview()
-                if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) {
-                    notifyIsInDesktopModeChanged(DEFAULT_DISPLAY, areDesktopTasksVisibleNow)
+
+                if (
+                    wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow ||
+                        wasVisible != isVisible
+                ) {
+                    if (!launcherAnimationRunning) {
+                        notifyIsInDesktopModeChanged(DEFAULT_DISPLAY, areDesktopTasksVisibleNow)
+                    } else {
+                        isNotifyingDesktopVisibilityPending = true
+                    }
                 }
 
                 if (
@@ -169,7 +197,7 @@
     /** Returns whether a desk is currently active on the display with the given [displayId]. */
     fun isInDesktopMode(displayId: Int): Boolean {
         if (!DesktopModeStatus.enableMultipleDesktops(context)) {
-            return areDesktopTasksVisible()
+            return isInDesktopMode
         }
 
         val activeDeskId = getDisplayDeskConfig(displayId)?.activeDeskId ?: INACTIVE_DESK_ID
@@ -196,15 +224,6 @@
     }
 
     /** Whether desktop tasks are visible in desktop mode. */
-    private fun areDesktopTasksVisible(): Boolean {
-        val desktopTasksVisible: Boolean = visibleDesktopTasksCount > 0
-        if (DEBUG) {
-            Log.d(TAG, "areDesktopTasksVisible: desktopVisible=$desktopTasksVisible")
-        }
-        return desktopTasksVisible
-    }
-
-    /** Whether desktop tasks are visible in desktop mode. */
     private fun areDesktopTasksVisibleAndNotInOverview(): Boolean {
         val desktopTasksVisible: Boolean = visibleDesktopTasksCount > 0
         if (DEBUG) {
@@ -237,6 +256,22 @@
         )
     }
 
+    /**
+     * Launcher Driven Desktop Mode changes. For example, swipe to home and quick switch from
+     * Desktop Windowing Mode. if there is any pending notification please notify desktop visibility
+     * listeners.
+     */
+    fun onLauncherAnimationFromDesktopEnd() {
+        launcherAnimationRunning = false
+        if (isNotifyingDesktopVisibilityPending) {
+            isNotifyingDesktopVisibilityPending = false
+            notifyIsInDesktopModeChanged(
+                DEFAULT_DISPLAY,
+                isInDesktopModeAndNotInOverview(DEFAULT_DISPLAY),
+            )
+        }
+    }
+
     fun onLauncherStateChanged(state: RecentsState) {
         onLauncherStateChanged(
             state,
@@ -346,6 +381,26 @@
         }
     }
 
+    private fun notifyTaskbarDesktopModeListenersForEntry(duration: Int) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyTaskbarDesktopModeListenersForEntry: duration=" + duration)
+        }
+        for (listener in taskbarDesktopModeListeners) {
+            listener.onEnterDesktopMode(duration)
+        }
+        DisplayController.INSTANCE.get(context).notifyConfigChange()
+    }
+
+    private fun notifyTaskbarDesktopModeListenersForExit(duration: Int) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyTaskbarDesktopModeListenersForExit: duration=" + duration)
+        }
+        for (listener in taskbarDesktopModeListeners) {
+            listener.onExitDesktopMode(duration)
+        }
+        DisplayController.INSTANCE.get(context).notifyConfigChange()
+    }
+
     /** TODO: b/333533253 - Remove after flag rollout */
     private fun setBackgroundStateEnabled(backgroundStateEnabled: Boolean) {
         if (DEBUG) {
@@ -552,14 +607,14 @@
             displayDeskStates: Array<DisplayDeskState>,
             canCreateDesks: Boolean,
         ) {
-            Executors.MAIN_EXECUTOR.execute {
+            MAIN_EXECUTOR.execute {
                 controller.get()?.onListenerConnected(displayDeskStates, canCreateDesks)
             }
         }
 
         override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
             if (displayId != this.displayId) return
-            Executors.MAIN_EXECUTOR.execute {
+            MAIN_EXECUTOR.execute {
                 controller.get()?.apply {
                     if (DEBUG) {
                         Log.d(TAG, "desktop visible tasks count changed=$visibleTasksCount")
@@ -575,7 +630,7 @@
 
         override fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean) {
             if (!DesktopModeStatus.useRoundedCorners()) return
-            Executors.MAIN_EXECUTOR.execute {
+            MAIN_EXECUTOR.execute {
                 controller.get()?.apply {
                     Log.d(
                         TAG,
@@ -587,26 +642,46 @@
             }
         }
 
-        override fun onEnterDesktopModeTransitionStarted(transitionDuration: Int) {}
-
-        override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) {}
-
-        override fun onCanCreateDesksChanged(canCreateDesks: Boolean) {
-            Executors.MAIN_EXECUTOR.execute {
-                controller.get()?.onCanCreateDesksChanged(canCreateDesks)
+        override fun onEnterDesktopModeTransitionStarted(transitionDuration: Int) {
+            MAIN_EXECUTOR.execute {
+                Log.d(
+                    TAG,
+                    ("DesktopTaskListenerImpl: onEnterDesktopModeTransitionStarted with " +
+                        "duration= " +
+                        transitionDuration),
+                )
+                controller.get()?.isInDesktopMode = true
+                controller.get()?.notifyTaskbarDesktopModeListenersForEntry(transitionDuration)
             }
         }
 
+        override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) {
+            MAIN_EXECUTOR.execute {
+                Log.d(
+                    TAG,
+                    ("DesktopTaskListenerImpl: onExitDesktopModeTransitionStarted with " +
+                        "duration= " +
+                        transitionDuration),
+                )
+                controller.get()?.isInDesktopMode = false
+                controller.get()?.notifyTaskbarDesktopModeListenersForExit(transitionDuration)
+            }
+        }
+
+        override fun onCanCreateDesksChanged(canCreateDesks: Boolean) {
+            MAIN_EXECUTOR.execute { controller.get()?.onCanCreateDesksChanged(canCreateDesks) }
+        }
+
         override fun onDeskAdded(displayId: Int, deskId: Int) {
-            Executors.MAIN_EXECUTOR.execute { controller.get()?.onDeskAdded(displayId, deskId) }
+            MAIN_EXECUTOR.execute { controller.get()?.onDeskAdded(displayId, deskId) }
         }
 
         override fun onDeskRemoved(displayId: Int, deskId: Int) {
-            Executors.MAIN_EXECUTOR.execute { controller.get()?.onDeskRemoved(displayId, deskId) }
+            MAIN_EXECUTOR.execute { controller.get()?.onDeskRemoved(displayId, deskId) }
         }
 
         override fun onActiveDeskChanged(displayId: Int, newActiveDesk: Int, oldActiveDesk: Int) {
-            Executors.MAIN_EXECUTOR.execute {
+            MAIN_EXECUTOR.execute {
                 controller.get()?.onActiveDeskChanged(displayId, newActiveDesk, oldActiveDesk)
             }
         }
@@ -619,7 +694,21 @@
          *
          * @param doesAnyTaskRequireTaskbarRounding whether task requires taskbar corner roundness.
          */
-        fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean)
+        fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean) {}
+
+        /**
+         * Callback for when user is exiting desktop mode.
+         *
+         * @param duration for exit transition
+         */
+        fun onExitDesktopMode(duration: Int) {}
+
+        /**
+         * Callback for when user is entering desktop mode.
+         *
+         * @param duration for enter transition
+         */
+        fun onEnterDesktopMode(duration: Int) {}
     }
 
     companion object {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 2e42e45..57bcc14 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -27,6 +27,7 @@
 
 import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
 
+import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
 import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP;
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
@@ -449,14 +450,25 @@
         mControllers.taskbarViewController.adjustTaskbarForBubbleBar();
     }
 
-    public void init(@NonNull TaskbarSharedState sharedState) {
+    /**
+     * Init of taskbar activity context.
+     * @param duration If duration is greater than 0, it will be used to create an animation
+ *                     for the taskbar create/recreate process.
+     */
+    public void init(@NonNull TaskbarSharedState sharedState, int duration) {
         mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, getResources(), false);
         mLastRequestedNonFullscreenSize = getDefaultTaskbarWindowSize();
         mWindowLayoutParams = createAllWindowParams();
         mLastUpdatedLayoutParams = new WindowManager.LayoutParams();
 
+
+        AnimatorSet recreateAnim = null;
+        if (duration > 0) {
+            recreateAnim = onRecreateAnimation(duration);
+        }
+
         // Initialize controllers after all are constructed.
-        mControllers.init(sharedState);
+        mControllers.init(sharedState, recreateAnim);
         // This may not be necessary and can be reverted once we move towards recreating all
         // controllers without re-creating the window
         mControllers.rotationButtonController.onNavigationModeChanged(mNavMode.resValue);
@@ -484,6 +496,33 @@
         } else {
             notifyUpdateLayoutParams();
         }
+
+
+        if (recreateAnim != null) {
+            recreateAnim.start();
+        }
+    }
+
+    /**
+     * Create AnimatorSet for taskbar create/recreate animation. Further used in init
+     */
+    public AnimatorSet onRecreateAnimation(int duration) {
+        AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.setDuration(duration);
+        return animatorSet;
+    }
+
+    /**
+     * Called when we want destroy current taskbar with animation as part of recreate process.
+     */
+    public AnimatorSet onDestroyAnimation(int duration) {
+        mIsDestroyed = true;
+        AnimatorSet animatorSet = new AnimatorSet();
+        mControllers.taskbarViewController.onDestroyAnimation(animatorSet);
+        mControllers.taskbarDragLayerController.onDestroyAnimation(animatorSet);
+        animatorSet.setInterpolator(LINEAR);
+        animatorSet.setDuration(duration);
+        return animatorSet;
     }
 
     /**
@@ -1983,6 +2022,10 @@
         return mControllers.taskbarStashController.isInApp();
     }
 
+    public boolean isInOverview() {
+        return mControllers.taskbarStashController.isInOverview();
+    }
+
     public boolean isInStashedLauncherState() {
         return mControllers.taskbarStashController.isInStashedLauncherState();
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index 6d23853..89cc991 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -50,6 +50,8 @@
         }
 
     var isAnimatingPinning = false
+    var isAnimatingPersistentTaskbar = false
+    var isAnimatingTransientTaskbar = false
 
     val paint = Paint()
     private val strokePaint = Paint()
@@ -144,7 +146,7 @@
     /** Draws the background with the given paint and height, on the provided canvas. */
     fun draw(canvas: Canvas) {
         if (isInSetup) return
-        val isTransientTaskbar = backgroundProgress == 0f
+        val isTransientTaskbar = DisplayController.isTransientTaskbar(context)
         canvas.save()
         if (!isTransientTaskbar || transientBackgroundBounds.isEmpty || isAnimatingPinning) {
             drawPersistentBackground(canvas)
@@ -158,7 +160,7 @@
     }
 
     private fun drawPersistentBackground(canvas: Canvas) {
-        if (isAnimatingPinning) {
+        if (isAnimatingPinning || isAnimatingPersistentTaskbar) {
             val persistentTaskbarHeight = maxPersistentTaskbarHeight * backgroundProgress
             canvas.translate(0f, canvas.height - persistentTaskbarHeight)
             // Draw the background behind taskbar content.
@@ -181,12 +183,13 @@
     private fun drawTransientBackground(canvas: Canvas) {
         val res = context.resources
         val transientTaskbarHeight = maxTransientTaskbarHeight * (1f - backgroundProgress)
+        val isAnimating = isAnimatingPinning || isAnimatingTransientTaskbar
         val heightProgressWhileAnimating =
-            if (isAnimatingPinning) transientTaskbarHeight else backgroundHeight
+            if (isAnimating) transientTaskbarHeight else backgroundHeight
 
         var progress = heightProgressWhileAnimating / maxTransientTaskbarHeight
         progress = Math.round(progress * 100f) / 100f
-        if (isAnimatingPinning) {
+        if (isAnimating) {
             var scale = transientTaskbarHeight / maxTransientTaskbarHeight
             scale = Math.round(scale * 100f) / 100f
             bottomMargin =
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index b244be9..6ca9385 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.taskbar;
 
+import android.animation.AnimatorSet;
 import android.content.pm.ActivityInfo.Config;
 
 import androidx.annotation.NonNull;
@@ -149,15 +150,15 @@
      * TaskbarControllers instance, but should be careful to only access things that were created
      * in constructors for now, as some controllers may still be waiting for init().
      */
-    public void init(@NonNull TaskbarSharedState sharedState) {
+    public void init(@NonNull TaskbarSharedState sharedState, AnimatorSet startAnimation) {
         mAreAllControllersInitialized = false;
         mSharedState = sharedState;
 
         taskbarDragController.init(this);
         navbarButtonsViewController.init(this);
         rotationButtonController.init();
-        taskbarDragLayerController.init(this);
-        taskbarViewController.init(this);
+        taskbarDragLayerController.init(this, startAnimation);
+        taskbarViewController.init(this, startAnimation);
         taskbarScrimViewController.init(this);
         taskbarUnfoldAnimationController.init(this);
         taskbarKeyguardController.init(navbarButtonsViewController);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
index f71dea9..ca8e4ca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
@@ -30,6 +30,9 @@
     private lateinit var taskbarControllers: TaskbarControllers
     private lateinit var taskbarSharedState: TaskbarSharedState
 
+    val isInDesktopMode: Boolean
+        get() = desktopVisibilityController.isInDesktopMode
+
     fun init(controllers: TaskbarControllers, sharedState: TaskbarSharedState) {
         taskbarControllers = controllers
         taskbarSharedState = sharedState
@@ -42,6 +45,7 @@
         desktopVisibilityController.isInDesktopModeAndNotInOverview(displayId)
 
     override fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean) {
+        if (taskbarControllers.taskbarActivityContext.isDestroyed) return
         taskbarSharedState.showCornerRadiusInDesktopMode = doesAnyTaskRequireTaskbarRounding
         val cornerRadius = getTaskbarCornerRoundness(doesAnyTaskRequireTaskbarRounding)
         taskbarControllers.taskbarCornerRoundness.animateToValue(cornerRadius).start()
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 59ef577..4dbad8c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -186,6 +186,7 @@
 
     @Override
     protected void dispatchDraw(Canvas canvas) {
+        if (mContainer.isDestroyed()) return;
         float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
                 * (1f - mTaskbarBackgroundOffset);
         mBackgroundRenderer.setBackgroundHeight(backgroundHeight);
@@ -286,6 +287,21 @@
     }
 
     /**
+     * Sets animation boolean when only animating persistent taskbar.
+     */
+    public void setIsAnimatingPersistentTaskbarBackground(boolean animatingPersistentTaskbarBg) {
+        mBackgroundRenderer.setAnimatingPersistentTaskbar(animatingPersistentTaskbarBg);
+    }
+
+    /**
+     * Sets animation boolean when only animating transient taskbar.
+     */
+    public void setIsAnimatingTransientTaskbarBackground(boolean animatingTransientTaskbarBg) {
+        mBackgroundRenderer.setAnimatingTransientTaskbar(animatingTransientTaskbarBg);
+    }
+
+
+    /**
      * Sets the width percentage to inset the transient taskbar's background from the left and from
      * the right.
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 68c252a..55ecc37 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -15,9 +15,12 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
 
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Point;
@@ -29,6 +32,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.util.DimensionUtils;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
@@ -58,6 +62,8 @@
     private final AnimatedFloat mImeBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
     private final AnimatedFloat mAssistantBgTaskbar = new AnimatedFloat(
             this::updateBackgroundAlpha);
+    private final AnimatedFloat mBgTaskbarRecreate = new AnimatedFloat(
+            this::updateBackgroundAlpha);
     // Used to hide our background color when someone else (e.g. ScrimView) is handling it.
     private final AnimatedFloat mBgOverride = new AnimatedFloat(this::updateBackgroundAlpha);
 
@@ -88,7 +94,10 @@
         mFolderMargin = resources.getDimensionPixelSize(R.dimen.taskbar_folder_margin);
     }
 
-    public void init(TaskbarControllers controllers) {
+    /**
+     * Init of taskbar drag layer controller
+     */
+    public void init(TaskbarControllers controllers, AnimatorSet startAnimation) {
         mControllers = controllers;
         mTaskbarStashViaTouchController = new TaskbarStashViaTouchController(mControllers);
         mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());
@@ -96,15 +105,45 @@
         mOnBackgroundNavButtonColorIntensity = mControllers.navbarButtonsViewController
                 .getOnTaskbarBackgroundNavButtonColorOverride();
 
-        mTaskbarBackgroundProgress.updateValue(DisplayController.isTransientTaskbar(mActivity)
-                ? PINNING_TRANSIENT
-                : PINNING_PERSISTENT);
+
+        if (startAnimation != null) {
+            // set taskbar background render animation boolean
+            if (DisplayController.isTransientTaskbar(mActivity)) {
+                mTaskbarDragLayer.setIsAnimatingTransientTaskbarBackground(true);
+            } else {
+                mTaskbarDragLayer.setIsAnimatingPersistentTaskbarBackground(true);
+            }
+
+            float desiredValue = DisplayController.isTransientTaskbar(mActivity)
+                    ? PINNING_TRANSIENT
+                    : PINNING_PERSISTENT;
+
+            float nonDesiredvalue = !DisplayController.isTransientTaskbar(mActivity)
+                    ? PINNING_TRANSIENT
+                    : PINNING_PERSISTENT;
+
+            ObjectAnimator objectAnimator = mTaskbarBackgroundProgress.animateToValue(
+                    nonDesiredvalue, desiredValue);
+            objectAnimator.setInterpolator(EMPHASIZED);
+            startAnimation.play(objectAnimator);
+            startAnimation.addListener(AnimatorListeners.forEndCallback(()-> {
+                // reset taskbar background render animation boolean
+                mTaskbarDragLayer.setIsAnimatingPersistentTaskbarBackground(false);
+                mTaskbarDragLayer.setIsAnimatingTransientTaskbarBackground(false);
+            }));
+
+        } else {
+            mTaskbarBackgroundProgress.updateValue(DisplayController.isTransientTaskbar(mActivity)
+                    ? PINNING_TRANSIENT
+                    : PINNING_PERSISTENT);
+        }
 
         mBgTaskbar.value = 1;
         mKeyguardBgTaskbar.value = 1;
         mNotificationShadeBgTaskbar.value = 1;
         mImeBgTaskbar.value = 1;
         mAssistantBgTaskbar.value = 1;
+        mBgTaskbarRecreate.value = 1;
         mBgOverride.value = 1;
         updateBackgroundAlpha();
 
@@ -112,6 +151,13 @@
         updateTaskbarAlpha();
     }
 
+    /**
+     * Called when destroying Taskbar with animation.
+     */
+    public void onDestroyAnimation(AnimatorSet animatorSet) {
+        animatorSet.play(mBgTaskbarRecreate.animateToValue(0f));
+    }
+
     public void onDestroy() {
         mTaskbarDragLayer.onDestroy();
     }
@@ -172,14 +218,14 @@
     }
 
     private void updateBackgroundAlpha() {
-        if (mActivity.isPhoneMode()) {
+        if (mActivity.isPhoneMode() || mActivity.isDestroyed()) {
             return;
         }
 
         final float bgNavbar = mBgNavbar.value;
         final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value
                 * mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value
-                * mAssistantBgTaskbar.value;
+                * mAssistantBgTaskbar.value * mBgTaskbarRecreate.value;
         mLastSetBackgroundAlpha = mBgOverride.value * Math.max(bgNavbar, bgTaskbar);
         mBackgroundRendererAlpha.setValue(mLastSetBackgroundAlpha);
 
@@ -266,6 +312,7 @@
         pw.println(prefix + "\t\tmNotificationShadeBgTaskbar=" + mNotificationShadeBgTaskbar.value);
         pw.println(prefix + "\t\tmImeBgTaskbar=" + mImeBgTaskbar.value);
         pw.println(prefix + "\t\tmAssistantBgTaskbar=" + mAssistantBgTaskbar.value);
+        pw.println(prefix + "\t\tmBgTaskbarRecreate=" + mBgTaskbarRecreate.value);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index b4ffb74..5d1288c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -39,6 +39,7 @@
 import com.airbnb.lottie.LottieAnimationView
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.R
+import com.android.launcher3.RemoveAnimationSettingsTracker
 import com.android.launcher3.Utilities
 import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
@@ -128,6 +129,26 @@
         activityContext.dragLayer.post { maybeShowSearchEdu() }
     }
 
+    /**
+     * Turns off auto play of lottie animations if user has opted to remove animation else attaches
+     * click listener to allow user to play or pause animations.
+     */
+    fun handleEduAnimations(animationViews: List<LottieAnimationView>) {
+        for (animationView in animationViews) {
+            if (
+                RemoveAnimationSettingsTracker.INSTANCE.get(animationView.context)
+                    .isRemoveAnimationEnabled()
+            ) {
+                animationView.pauseAnimation()
+            } else {
+                animationView.setOnClickListener {
+                    if (animationView.isAnimating) animationView.pauseAnimation()
+                    else animationView.playAnimation()
+                }
+            }
+        }
+    }
+
     /** Shows swipe EDU tooltip if it is the current [tooltipStep]. */
     fun maybeShowSwipeEdu() {
         if (
@@ -145,7 +166,9 @@
                 requireViewById(R.id.taskbar_edu_title),
                 TypefaceUtils.FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED,
             )
-            requireViewById<LottieAnimationView>(R.id.swipe_animation).supportLightTheme()
+            val swipeAnimation = requireViewById<LottieAnimationView>(R.id.swipe_animation)
+            swipeAnimation.supportLightTheme()
+            handleEduAnimations(listOf(swipeAnimation))
             show()
         }
     }
@@ -174,6 +197,7 @@
             splitscreenAnim.supportLightTheme()
             suggestionsAnim.supportLightTheme()
             pinningAnim.supportLightTheme()
+            handleEduAnimations(listOf(splitscreenAnim, suggestionsAnim, pinningAnim))
             if (DisplayController.isTransientTaskbar(activityContext)) {
                 splitscreenAnim.setAnimation(R.raw.taskbar_edu_splitscreen_transient)
                 suggestionsAnim.setAnimation(R.raw.taskbar_edu_suggestions_transient)
@@ -249,9 +273,6 @@
 
         tooltip?.run {
             allowTouchDismissal = true
-            requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
-                .supportLightTheme()
-
             TypefaceUtils.setTypeface(
                 requireViewById(R.id.taskbar_edu_title),
                 TypefaceUtils.FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED,
@@ -261,6 +282,10 @@
                 TypefaceUtils.FONT_FAMILY_BODY_MEDIUM_BASELINE,
             )
 
+            val pinningAnim =
+                requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
+            pinningAnim.supportLightTheme()
+            handleEduAnimations(listOf(pinningAnim))
             updateLayoutParams<BaseDragLayer.LayoutParams> {
                 if (DisplayController.isTransientTaskbar(activityContext)) {
                     bottomMargin += activityContext.deviceProfile.taskbarHeight
@@ -304,7 +329,9 @@
         inflateTooltip(R.layout.taskbar_edu_search)
         tooltip?.run {
             allowTouchDismissal = true
-            requireViewById<LottieAnimationView>(R.id.search_edu_animation).supportLightTheme()
+            val searchEdu = requireViewById<LottieAnimationView>(R.id.search_edu_animation)
+            searchEdu.supportLightTheme()
+            handleEduAnimations(listOf(searchEdu))
             val eduSubtitle: TextView = requireViewById(R.id.search_edu_text)
 
             TypefaceUtils.setTypeface(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index dd9f61e..10eb64a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -223,9 +223,13 @@
                     updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
                     if (!mShouldDelayLauncherStateAnim) {
                         if (toState == LauncherState.NORMAL) {
-                            applyState(QuickstepTransitionManager.getTaskbarToHomeDuration(
+                            boolean isPinnedTaskbarAndNotInDesktopMode =
                                     DisplayController.isPinnedTaskbar(
-                                            mControllers.taskbarActivityContext)));
+                                            mControllers.taskbarActivityContext)
+                                            && !DisplayController.isInDesktopMode(
+                                            mControllers.taskbarActivityContext);
+                            applyState(QuickstepTransitionManager.getTaskbarToHomeDuration(
+                                    isPinnedTaskbarAndNotInDesktopMode));
                         } else {
                             applyState();
                         }
@@ -680,8 +684,11 @@
         } else if (mIconAlignment.isAnimatingToValue(toAlignment)
                 || mIconAlignment.isSettledOnValue(toAlignment)) {
             // Already at desired value, but make sure we run the callback at the end.
-            animatorSet.addListener(AnimatorListeners.forEndCallback(
-                    this::onIconAlignmentRatioChanged));
+            animatorSet.addListener(AnimatorListeners.forEndCallback(() -> {
+                if (!mIconAlignment.isAnimating()) {
+                    onIconAlignmentRatioChanged();
+                }
+            }));
         } else {
             mIconAlignment.cancelAnimation();
             ObjectAnimator iconAlignAnim = mIconAlignment
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 34bb6e0..0fec4e6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 import static com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE;
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
+import static com.android.launcher3.util.DisplayController.CHANGE_SHOW_LOCKED_TASKBAR;
 import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
 import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -33,6 +34,7 @@
 import static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR;
 import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
 
+import android.animation.AnimatorSet;
 import android.annotation.SuppressLint;
 import android.app.PendingIntent;
 import android.content.ComponentCallbacks;
@@ -60,9 +62,12 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
 import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
@@ -101,6 +106,7 @@
     // TODO(b/382378283) remove all logs with this tag
     public static final String NULL_TASKBAR_ROOT_LAYOUT_TAG = "b/382378283";
     public static final String ILLEGAL_ARGUMENT_WM_ADD_VIEW = "b/391653300";
+    private static final int TASKBAR_DESTROY_DURATION = 100;
 
     /**
      * All the configurations which do not initiate taskbar recreation.
@@ -153,6 +159,8 @@
             new SparseArray<>();
     /** DisplayId - {@link ComponentCallbacks} map for Connected Display. */
     private final SparseArray<ComponentCallbacks> mComponentCallbacks = new SparseArray<>();
+    /** DisplayId - {@link DeviceProfile} map for Connected Display. */
+    private final SparseArray<DeviceProfile> mExternalDeviceProfiles = new SparseArray<>();
     private StatefulActivity mActivity;
     private RecentsViewContainer mRecentsViewContainer;
 
@@ -189,13 +197,28 @@
             }
 
             if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE | CHANGE_DESKTOP_MODE
-                    | CHANGE_TASKBAR_PINNING)) != 0) {
+                    | CHANGE_TASKBAR_PINNING | CHANGE_SHOW_LOCKED_TASKBAR)) != 0) {
                 debugTaskbarManager("onDisplayInfoChanged - Recreating Taskbar!",
                         context.getDisplayId());
-                recreateTaskbar();
+                TaskbarActivityContext taskbarActivityContext = getCurrentActivityContext();
+                if ((flags & CHANGE_SHOW_LOCKED_TASKBAR) != 0) {
+                    recreateTaskbar();
+                } else if ((flags & CHANGE_DESKTOP_MODE) != 0) {
+                    // Only Handles Special Exit Cases for Desktop Mode Taskbar Recreation.
+                    if (taskbarActivityContext != null
+                            && !DesktopVisibilityController.INSTANCE.get(taskbarActivityContext)
+                            .isInDesktopMode()
+                            && !DisplayController.showLockedTaskbarOnHome(context)) {
+                        recreateTaskbar();
+                    }
+                } else {
+                    recreateTaskbar();
+                }
+
             }
         }
     }
+
     private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> {
         debugTaskbarManager("Settings changed! Recreating Taskbar!");
         recreateTaskbar();
@@ -258,7 +281,52 @@
         public void onTaskRemoved(int taskId) {
             mPerceptibleTasks.remove(taskId);
         }
-    };
+    }
+
+    ;
+
+    private final DesktopVisibilityController.TaskbarDesktopModeListener
+            mTaskbarDesktopModeListener =
+            new DesktopVisibilityController.TaskbarDesktopModeListener() {
+                @Override
+                public void onExitDesktopMode(int duration) {
+                    for (int taskbarIndex = 0; taskbarIndex < mTaskbars.size(); taskbarIndex++) {
+                        int displayId = mTaskbars.keyAt(taskbarIndex);
+                        TaskbarActivityContext taskbarActivityContext = getTaskbarForDisplay(
+                                displayId);
+                        if (taskbarActivityContext != null
+                                && !taskbarActivityContext.isInOverview()) {
+                            AnimatorSet animatorSet = taskbarActivityContext.onDestroyAnimation(
+                                    TASKBAR_DESTROY_DURATION);
+                            animatorSet.addListener(AnimatorListeners.forEndCallback(
+                                    () -> recreateTaskbarForDisplay(getDefaultDisplayId(),
+                                            duration)));
+                            animatorSet.start();
+                        }
+                    }
+                }
+
+                @Override
+                public void onEnterDesktopMode(int duration) {
+                    for (int taskbarIndex = 0; taskbarIndex < mTaskbars.size(); taskbarIndex++) {
+                        int displayId = mTaskbars.keyAt(taskbarIndex);
+                        TaskbarActivityContext taskbarActivityContext = getTaskbarForDisplay(
+                                displayId);
+                        AnimatorSet animatorSet = taskbarActivityContext.onDestroyAnimation(
+                                TASKBAR_DESTROY_DURATION);
+                        animatorSet.addListener(AnimatorListeners.forEndCallback(
+                                () -> recreateTaskbarForDisplay(getDefaultDisplayId(), duration)));
+                        animatorSet.start();
+                    }
+                }
+
+                @Override
+                public void onTaskbarCornerRoundingUpdate(
+                        boolean doesAnyTaskRequireTaskbarRounding) {
+                    //NO-OP
+                }
+            };
+
 
     private boolean mUserUnlocked = false;
 
@@ -337,6 +405,9 @@
         debugTaskbarManager("TaskbarManager constructor", primaryDisplayId);
         mPrimaryWindowContext = createWindowContext(primaryDisplayId);
         mPrimaryWindowManager = mPrimaryWindowContext.getSystemService(WindowManager.class);
+        DesktopVisibilityController.INSTANCE.get(
+                mPrimaryWindowContext).registerTaskbarDesktopModeListener(
+                mTaskbarDesktopModeListener);
         createTaskbarRootLayout(primaryDisplayId);
         createNavButtonController(primaryDisplayId);
         createAndRegisterComponentCallbacks(primaryDisplayId);
@@ -389,9 +460,8 @@
             // remove all defaults that we store
             removeTaskbarFromMap(displayId);
         }
-        // TODO (b/381113004): make this display-specific via getWindowContext()
-        DeviceProfile dp = mUserUnlocked ? LauncherAppState.getIDP(
-                mPrimaryWindowContext).getDeviceProfile(mPrimaryWindowContext) : null;
+
+        DeviceProfile dp = getDeviceProfile(displayId);
         if (dp == null || !isTaskbarEnabled(dp)) {
             removeTaskbarRootViewFromWindow(displayId);
         }
@@ -550,13 +620,13 @@
     public synchronized void recreateTaskbar() {
         // Handles initial creation case.
         if (mTaskbars.size() == 0) {
-            recreateTaskbarForDisplay(getDefaultDisplayId());
+            recreateTaskbarForDisplay(getDefaultDisplayId(), 0);
             return;
         }
 
         for (int i = 0; i < mTaskbars.size(); i++) {
             int displayId = mTaskbars.keyAt(i);
-            recreateTaskbarForDisplay(displayId);
+            recreateTaskbarForDisplay(displayId, 0);
         }
     }
 
@@ -565,13 +635,12 @@
      * we fully want to destroy an existing taskbar for a specified display and create a new one.
      * In other case (folding/unfolding) we don't need to remove and add window.
      */
-    private void recreateTaskbarForDisplay(int displayId) {
+    private void recreateTaskbarForDisplay(int displayId, int duration) {
         Trace.beginSection("recreateTaskbarForDisplay");
         try {
             Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "recreateTaskbarForDisplay: " + displayId);
             // TODO (b/381113004): make this display-specific via getWindowContext()
-            DeviceProfile dp = mUserUnlocked ? LauncherAppState.getIDP(
-                    mPrimaryWindowContext).getDeviceProfile(mPrimaryWindowContext) : null;
+            DeviceProfile dp = getDeviceProfile(displayId);
 
             // All Apps action is unrelated to navbar unification, so we only need to check DP.
             final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
@@ -582,13 +651,13 @@
             boolean displayExists = getDisplay(displayId) != null;
             boolean isTaskbarEnabled = dp != null && isTaskbarEnabled(dp);
             debugTaskbarManager("recreateTaskbarForDisplay: isTaskbarEnabled=" + isTaskbarEnabled
-                + " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
-                + " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
+                    + " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
+                    + " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
                     + " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent)
                     + " displayExists=" + displayExists);
             if (!isTaskbarEnabled || !isLargeScreenTaskbar || !displayExists) {
                 SystemUiProxy.INSTANCE.get(mBaseContext)
-                    .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
+                        .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
                 if (!isTaskbarEnabled || !displayExists) {
                     return;
                 }
@@ -608,7 +677,7 @@
             mSharedState.startTaskbarVariantIsTransient =
                     DisplayController.isTransientTaskbar(taskbar);
             mSharedState.allAppsVisible = mSharedState.allAppsVisible && isLargeScreenTaskbar;
-            taskbar.init(mSharedState);
+            taskbar.init(mSharedState, duration);
 
             // Non default displays should not use LauncherTaskbarUIController as they shouldn't
             // have access to the Launcher activity.
@@ -789,10 +858,11 @@
             if (wm == null || !wm.shouldShowSystemDecors(displayId)) {
                 return;
             }
+            createExternalDeviceProfile(displayId);
             createTaskbarRootLayout(displayId);
             createNavButtonController(displayId);
             createAndRegisterComponentCallbacks(displayId);
-            recreateTaskbarForDisplay(displayId);
+            recreateTaskbarForDisplay(displayId, 0);
         }
     }
 
@@ -811,6 +881,7 @@
             removeNavButtonController(displayId);
             removeAndUnregisterComponentCallbacks(displayId);
             destroyTaskbarForDisplay(displayId);
+            removeDeviceProfileFromMap(displayId);
             removeWindowContextFromMap(displayId);
         }
     }
@@ -845,6 +916,9 @@
     public void destroy() {
         mRecentsViewContainer = null;
         debugTaskbarManager("TaskbarManager#destroy()");
+        DesktopVisibilityController.INSTANCE.get(
+                mPrimaryWindowContext).unregisterTaskbarDesktopModeListener(
+                mTaskbarDesktopModeListener);
         removeActivityCallbacksAndListeners();
         mTaskbarBroadcastReceiver.unregisterReceiverSafely();
 
@@ -860,7 +934,7 @@
         removeAndUnregisterComponentCallbacks(getDefaultDisplayId());
         mShutdownReceiver.unregisterReceiverSafely();
         if (ActivityManagerWrapper.usePerceptibleTasks(getPrimaryWindowContext())) {
-            for (Integer taskId: mTaskStackListener.mPerceptibleTasks) {
+            for (Integer taskId : mTaskStackListener.mPerceptibleTasks) {
                 ActivityManagerWrapper.getInstance().setTaskIsPerceptible(taskId, false);
             }
         }
@@ -940,7 +1014,7 @@
      *
      * @param displayId The ID of the display to retrieve the taskbar for.
      * @return The {@link TaskbarUIController} for the specified display, or
-     *         {@code null} if no taskbar is associated with that display.
+     * {@code null} if no taskbar is associated with that display.
      */
     @Nullable
     public TaskbarUIController getUIControllerForDisplay(int displayId) {
@@ -967,7 +1041,7 @@
      *
      * @param displayId The ID of the display to retrieve the taskbar for.
      * @return The {@link TaskbarActivityContext} for the specified display, or
-     *         {@code null} if no taskbar is associated with that display.
+     * {@code null} if no taskbar is associated with that display.
      */
     private TaskbarActivityContext getTaskbarForDisplay(int displayId) {
         return mTaskbars.get(displayId);
@@ -976,7 +1050,8 @@
 
     /**
      * Creates a {@link TaskbarActivityContext} for the given display and adds it to the map.
-     * @param dp The {@link DeviceProfile} for the display.
+     *
+     * @param dp        The {@link DeviceProfile} for the display.
      * @param displayId The ID of the display.
      */
     private @Nullable TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp,
@@ -1006,8 +1081,64 @@
     }
 
     /**
+     * Creates a {@link DeviceProfile} for the given display and adds it to the map.
+     * @param displayId The ID of the display.
+     */
+    private void createExternalDeviceProfile(int displayId) {
+        if (!mUserUnlocked) {
+            return;
+        }
+
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(mPrimaryWindowContext);
+        if (idp == null) {
+            return;
+        }
+
+        Context displayContext = getWindowContext(displayId);
+        if (displayContext == null) {
+            return;
+        }
+
+        DeviceProfile externalDeviceProfile = idp.createDeviceProfileForSecondaryDisplay(
+                displayContext);
+        mExternalDeviceProfiles.put(displayId, externalDeviceProfile);
+    }
+
+    /**
+     * Gets a {@link DeviceProfile} for the given displayId.
+     * @param displayId The ID of the display.
+     */
+    private @Nullable DeviceProfile getDeviceProfile(int displayId) {
+        if (!mUserUnlocked) {
+            return null;
+        }
+
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(mPrimaryWindowContext);
+        if (idp == null) {
+            return null;
+        }
+
+        boolean isPrimary = isDefaultDisplay(displayId)
+                || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue();
+        if (isPrimary) {
+            return idp.getDeviceProfile(mPrimaryWindowContext);
+        }
+
+        return mExternalDeviceProfiles.get(displayId);
+    }
+
+    /**
+     * Removes the {@link DeviceProfile} associated with the given display ID from the map.
+     * @param displayId The ID of the display for which to remove the taskbar.
+     */
+    private void removeDeviceProfileFromMap(int displayId) {
+        mExternalDeviceProfiles.delete(displayId);
+    }
+
+    /**
      * Create {@link ComponentCallbacks} for the given display and register it to the relevant
      * WindowContext. For external displays, populate maps.
+     *
      * @param displayId The ID of the display.
      */
     private void createAndRegisterComponentCallbacks(int displayId) {
@@ -1021,9 +1152,8 @@
                         "onConfigurationChanged: " + newConfig);
                 debugTaskbarManager(
                         "TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
-                // TODO (b/381113004): make this display-specific via getWindowContext()
-                DeviceProfile dp = mUserUnlocked ? LauncherAppState.getIDP(
-                        mPrimaryWindowContext).getDeviceProfile(mPrimaryWindowContext) : null;
+
+                DeviceProfile dp = getDeviceProfile(displayId);
                 int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
 
                 if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
@@ -1065,7 +1195,8 @@
             }
 
             @Override
-            public void onLowMemory() { }
+            public void onLowMemory() {
+            }
         };
         if (isDefaultDisplay(displayId)
                 || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
@@ -1080,6 +1211,7 @@
     /**
      * Unregister {@link ComponentCallbacks} for the given display from its WindowContext. For
      * external displays, remove from the map.
+     *
      * @param displayId The ID of the display.
      */
     private void removeAndUnregisterComponentCallbacks(int displayId) {
@@ -1096,6 +1228,7 @@
     /**
      * Creates a {@link TaskbarNavButtonController} for the given display and adds it to the map
      * if it doesn't already exist.
+     *
      * @param displayId The ID of the display
      */
     private void createNavButtonController(int displayId) {
@@ -1137,7 +1270,7 @@
      * Adds the {@link TaskbarActivityContext} associated with the given display ID to taskbar
      * map if there is not already a taskbar mapped to that displayId.
      *
-     * @param displayId The ID of the display to retrieve the taskbar for.
+     * @param displayId  The ID of the display to retrieve the taskbar for.
      * @param newTaskbar The new {@link TaskbarActivityContext} to add to the map.
      */
     private void addTaskbarToMap(int displayId, TaskbarActivityContext newTaskbar) {
@@ -1157,6 +1290,7 @@
 
     /**
      * Creates {@link FrameLayout} for the taskbar on the specified display and adds it to map.
+     *
      * @param displayId The ID of the display for which to create the taskbar root layout.
      */
     private void createTaskbarRootLayout(int displayId) {
@@ -1206,7 +1340,7 @@
     /**
      * Adds the taskbar root layout {@link FrameLayout} to taskbar map, mapped to display ID.
      *
-     * @param displayId The ID of the display to associate with the taskbar root layout.
+     * @param displayId  The ID of the display to associate with the taskbar root layout.
      * @param rootLayout The taskbar root layout {@link FrameLayout} to add to the map.
      */
     private void addTaskbarRootLayoutToMap(int displayId, FrameLayout rootLayout) {
@@ -1233,6 +1367,7 @@
 
     /**
      * Creates {@link Context} for the taskbar on the specified display.
+     *
      * @param displayId The ID of the display for which to create the window context.
      */
     private @Nullable Context createWindowContext(int displayId) {
@@ -1312,7 +1447,7 @@
     /**
      * Adds the window context {@link Context} to taskbar map, mapped to display ID.
      *
-     * @param displayId The ID of the display to associate with the taskbar root layout.
+     * @param displayId     The ID of the display to associate with the taskbar root layout.
      * @param windowContext The window context {@link Context} to add to the map.
      */
     private void addWindowContextToMap(int displayId, @NonNull Context windowContext) {
@@ -1356,8 +1491,11 @@
             return;
         }
 
-        boolean contextTaskbarPresent = mUserUnlocked && LauncherAppState.getIDP(windowContext)
-                .getDeviceProfile(windowContext).isTaskbarPresent;
+        boolean contextTaskbarPresent = false;
+        if (mUserUnlocked) {
+            DeviceProfile dp = getDeviceProfile(displayId);
+            contextTaskbarPresent = dp != null && dp.isTaskbarPresent;
+        }
         if (activityTaskbarPresent == contextTaskbarPresent) {
             log.add("mActivity and mWindowContext agree taskbarIsPresent=" + contextTaskbarPresent);
             Log.d(TASKBAR_NOT_DESTROYED_TAG, log.toString());
@@ -1379,8 +1517,8 @@
         log.add("\t\tWindowContext.getResources().getConfiguration()="
                 + windowContext.getResources().getConfiguration());
         if (mUserUnlocked) {
-            log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(mPrimaryWindowContext)"
-                    + ".isTaskbarPresent=" + contextTaskbarPresent);
+            log.add("\t\tgetDeviceProfile(mPrimaryWindowContext).isTaskbarPresent="
+                    + contextTaskbarPresent);
         } else {
             log.add("\t\tCouldn't get DeviceProfile because !mUserUnlocked");
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index a59c9e3..c92f20b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -392,6 +392,7 @@
 
     /** Inflates/binds the hotseat items and recent tasks to the view. */
     protected void updateItems(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
+        if (mActivityContext.isDestroyed()) return;
         // Filter out unsupported items.
         hotseatItemInfos = Arrays.stream(hotseatItemInfos)
                 .filter(Objects::nonNull)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index cbc5d3d..384468c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -121,9 +121,10 @@
     public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4;
     public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 5;
     public static final int ALPHA_INDEX_SMALL_SCREEN = 6;
-
     public static final int ALPHA_INDEX_BUBBLE_BAR = 7;
-    private static final int NUM_ALPHA_CHANNELS = 8;
+    public static final int ALPHA_INDEX_RECREATE = 8;
+
+    private static final int NUM_ALPHA_CHANNELS = 9;
 
     /** Only used for animation purposes, to position the divider between two item indices. */
     public static final float DIVIDER_VIEW_POSITION_OFFSET = 0.5f;
@@ -238,9 +239,22 @@
                 R.dimen.transient_taskbar_padding);
     }
 
-    public void init(TaskbarControllers controllers) {
+    /**
+     * Init of taskbar view controller.
+     */
+    public void init(TaskbarControllers controllers, AnimatorSet startAnimation) {
         mControllers = controllers;
         controllers.bubbleControllers.ifPresent(bc -> mBubbleControllers = bc);
+
+        if (startAnimation != null) {
+            MultiPropertyFactory<View>.MultiProperty multiProperty =
+                    mTaskbarIconAlpha.get(ALPHA_INDEX_RECREATE);
+            multiProperty.setValue(0f);
+            Animator animator = multiProperty.animateToValue(1f);
+            animator.setInterpolator(EMPHASIZED);
+            startAnimation.play(animator);
+        }
+
         mTaskbarView.init(TaskbarViewCallbacksFactory.newInstance(mActivity).create(
                 mActivity, mControllers, mTaskbarView));
         mTaskbarView.getLayoutParams().height = mActivity.isPhoneMode()
@@ -362,6 +376,15 @@
         mTaskbarView.announceAccessibilityChanges();
     }
 
+    /**
+     * Called with destroying Taskbar with animation.
+     */
+    public void onDestroyAnimation(AnimatorSet animatorSet) {
+        animatorSet.play(
+                mTaskbarIconAlpha.get(TaskbarViewController.ALPHA_INDEX_RECREATE).animateToValue(
+                        0f));
+    }
+
     public void onDestroy() {
         if (enableTaskbarPinning()) {
             mTaskbarView.removeOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
@@ -1299,7 +1322,7 @@
         ObjectAnimator animator = mIconsTranslationXForNavbar.animateToValue(translationX);
         animator.setStartDelay(FADE_OUT_ANIM_POSITION_DURATION_MS);
         animator.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
-        animator.setInterpolator(Interpolators.EMPHASIZED);
+        animator.setInterpolator(EMPHASIZED);
         return animator;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt b/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt
index 8b53ff1..e9c62d1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt
@@ -30,11 +30,14 @@
 class TypefaceUtils {
 
     companion object {
-        const val FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED = "variable-headline-small-emphasized"
-        const val FONT_FAMILY_HEADLINE_LARGE_EMPHASIZED = "variable-headline-large-emphasized"
         const val FONT_FAMILY_BODY_SMALL_BASELINE = "variable-body-small"
         const val FONT_FAMILY_BODY_MEDIUM_BASELINE = "variable-body-medium"
+        const val FONT_FAMILY_BODY_LARGE_BASELINE = "variable-body-large"
         const val FONT_FAMILY_LABEL_LARGE_BASELINE = "variable-label-large"
+        const val FONT_FAMILY_DISPLAY_SMALL_EMPHASIZED = "variable-display-small-emphasized"
+        const val FONT_FAMILY_DISPLAY_MEDIUM_EMPHASIZED = "variable-display-medium-emphasized"
+        const val FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED = "variable-headline-small-emphasized"
+        const val FONT_FAMILY_HEADLINE_LARGE_EMPHASIZED = "variable-headline-large-emphasized"
 
         @JvmStatic
         @JvmOverloads
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
index cca8bf8..04e1905 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
@@ -27,17 +27,16 @@
 import com.android.launcher3.anim.AnimatorListeners.forSuccessCallback
 import com.android.launcher3.anim.PendingAnimation
 import com.android.launcher3.anim.PropertySetter
-import com.android.launcher3.logging.StatsLogManager.LauncherEvent
 import com.android.launcher3.statemanager.StateManager.StateHandler
 import com.android.launcher3.states.StateAnimationConfig
 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE
 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE
 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL
 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE
-import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE
 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X
 import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y
 import com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW
+import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
 import com.android.quickstep.util.AnimUtils
 import com.android.quickstep.views.ClearAllButton
 import com.android.quickstep.views.RecentsView
@@ -226,8 +225,8 @@
         builder: PendingAnimation,
         animate: Boolean,
     ) {
-        val goingToOverviewFromWorkspaceContextual = toState == LauncherState.OVERVIEW &&
-                launcher.isSplitSelectionActive
+        val goingToOverviewFromWorkspaceContextual =
+            toState == LauncherState.OVERVIEW && launcher.isSplitSelectionActive
         if (
             toState != LauncherState.OVERVIEW_SPLIT_SELECT &&
                 !goingToOverviewFromWorkspaceContextual
@@ -302,6 +301,14 @@
             overviewButtonAlpha,
             config.getInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, LINEAR),
         )
+        recentsView.addDeskButton?.let {
+            propertySetter.setFloat(
+                it.visibilityAlphaProperty,
+                MULTI_PROPERTY_VALUE,
+                if (state.areElementsVisible(launcher, LauncherState.ADD_DESK_BUTTON)) 1f else 0f,
+                LINEAR,
+            )
+        }
     }
 
     private fun getOverviewInterpolator(fromState: LauncherState, toState: LauncherState) =
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index ca388c6..b1196af 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -77,7 +77,8 @@
         return super.getVisibleElements(launcher)
                 & ~OVERVIEW_ACTIONS
                 & ~CLEAR_ALL_BUTTON
-                & ~VERTICAL_SWIPE_INDICATOR;
+                & ~VERTICAL_SWIPE_INDICATOR
+                & ~ADD_DESK_BUTTON;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 80fc5fa..0c0b4fd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -45,7 +45,7 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        return OVERVIEW_ACTIONS | CLEAR_ALL_BUTTON;
+        return OVERVIEW_ACTIONS;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 15216fe..5fdedcc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -110,7 +110,7 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        int elements = CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
+        int elements = CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS | ADD_DESK_BUTTON;
         DeviceProfile dp = launcher.getDeviceProfile();
         boolean showFloatingSearch;
         if (dp.isPhone) {
@@ -124,7 +124,7 @@
             elements |= FLOATING_SEARCH_BAR;
         }
         if (launcher.isSplitSelectionActive()) {
-            elements &= ~CLEAR_ALL_BUTTON;
+            elements &= ~CLEAR_ALL_BUTTON & ~ADD_DESK_BUTTON;
         }
         return elements;
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 1907b4e..44f8bf1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -109,8 +109,11 @@
 
             // We sync the scrim fade with the taskbar animation duration to avoid any flickers for
             // taskbar icons disappearing before hotseat icons show up.
+            boolean isPinnedTaskbarAndNotInDesktopMode =
+                    isPinnedTaskbar && !DisplayController.isInDesktopMode(mContainer);
             float scrimUpperBoundFromSplit =
-                    QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar)
+                    QuickstepTransitionManager.getTaskbarToHomeDuration(
+                            isPinnedTaskbarAndNotInDesktopMode)
                             / (float) config.duration;
             scrimUpperBoundFromSplit = Math.min(scrimUpperBoundFromSplit, 1f);
             config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f));
@@ -142,7 +145,8 @@
                 if (mContainer.getDeviceProfile().isTaskbarPresent) {
                     config.duration = Math.min(
                             config.duration,
-                            QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar));
+                            QuickstepTransitionManager.getTaskbarToHomeDuration(
+                                    isPinnedTaskbarAndNotInDesktopMode));
                 }
                 overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
             } else {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 67a54e6..c51f659 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -120,6 +120,7 @@
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulContainer;
 import com.android.launcher3.taskbar.TaskbarThresholdUtils;
@@ -1421,8 +1422,10 @@
         }
         if (endTarget == HOME) {
             boolean isPinnedTaskbar = DisplayController.isPinnedTaskbar(mContext);
+            boolean isNotInDesktop =  !DisplayController.isInDesktopMode(mContext);
             duration = mContainer != null && mContainer.getDeviceProfile().isTaskbarPresent
-                    ? QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar)
+                    ? QuickstepTransitionManager.getTaskbarToHomeDuration(
+                    isPinnedTaskbar && isNotInDesktop)
                     : StaggeredWorkspaceAnim.DURATION_MS;
             SystemUiProxy.INSTANCE.get(mContext).updateContextualEduStats(
                     mGestureState.isTrackpadGesture(), GestureType.HOME);
@@ -1602,9 +1605,27 @@
             if (mParallelRunningAnim != null) {
                 mParallelRunningAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
+                    public void onAnimationStart(Animator animation) {
+                        if (DisplayController.isInDesktopMode(mContext)
+                                && mGestureState.getEndTarget() == HOME) {
+                            // Set launcher animation started, so we don't notify from
+                            // desktop visibility controller
+                            DesktopVisibilityController.INSTANCE.get(
+                                    mContext).setLauncherAnimationRunning(true);
+                        }
+                    }
+
+                    @Override
                     public void onAnimationEnd(Animator animation) {
                         mParallelRunningAnim = null;
                         mStateCallback.setStateOnUiThread(STATE_PARALLEL_ANIM_FINISHED);
+                        // Swipe to home animation finished, notify DesktopVisibilityController
+                        // to recreate Taskbar
+                        if (DisplayController.isInDesktopMode(mContext)
+                                && mGestureState.getEndTarget() == HOME) {
+                            DesktopVisibilityController.INSTANCE.get(
+                                    mContext).onLauncherAnimationFromDesktopEnd();
+                        }
                     }
                 });
                 mParallelRunningAnim.start();
@@ -1691,7 +1712,6 @@
                 if (mHandOffAnimationToHome) {
                     handOffAnimation(velocityPxPerMs);
                 }
-
                 windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
                     @Override
                     public void onAnimationSuccess(Animator animator) {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 56dd696..537092f 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
+import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
 import static com.android.quickstep.fallback.RecentsState.OVERVIEW_SPLIT_SELECT;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
 import static com.android.quickstep.views.RecentsView.DESKTOP_CAROUSEL_DETACH_PROGRESS;
@@ -99,6 +100,11 @@
         float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
         setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
                 clearAllButtonAlpha, LINEAR);
+        if (mRecentsView.getAddDeskButton() != null) {
+            float addDeskButtonAlpha = state.hasAddDeskButton() ? 1 : 0;
+            setter.setFloat(mRecentsView.getAddDeskButton().getVisibilityAlphaProperty(),
+                    MULTI_PROPERTY_VALUE, addDeskButtonAlpha, LINEAR);
+        }
         float overviewButtonAlpha = state.hasOverviewActions() ? 1 : 0;
         setter.setFloat(mRecentsViewContainer.getActionsView().getVisibilityAlpha(),
                 AnimatedFloat.VALUE, overviewButtonAlpha, LINEAR);
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index f27b60c..f722c5d 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -45,14 +45,16 @@
     private static final int FLAG_RECENTS_VIEW_VISIBLE = BaseState.getFlag(7);
     private static final int FLAG_TASK_THUMBNAIL_SPLASH = BaseState.getFlag(8);
     private static final int FLAG_DETACH_DESKTOP_CAROUSEL = BaseState.getFlag(9);
+    private static final int FLAG_ADD_DESK_BUTTON = BaseState.getFlag(10);
 
     private static final RecentsState[] sAllStates = new RecentsState[6];
 
     public static final RecentsState DEFAULT = new RecentsState(0,
             FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID
-                    | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE);
+                    | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE
+                    | FLAG_ADD_DESK_BUTTON);
     public static final RecentsState MODAL_TASK = new ModalState(1,
-            FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
+            FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
                     | FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE);
     public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
             FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN
@@ -122,6 +124,13 @@
     }
 
     /**
+     * For this state, whether add desk button should be shown.
+     */
+    public boolean hasAddDeskButton() {
+        return hasFlag(FLAG_ADD_DESK_BUTTON);
+    }
+
+    /**
      * For this state, whether overview actions should be shown.
      */
     public boolean hasOverviewActions() {
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 953b0c5..c1bb250 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -64,6 +64,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
+import com.android.launcher3.RemoveAnimationSettingsTracker;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -107,6 +108,9 @@
 
     private static final float ANIMATION_PAUSE_ALPHA_THRESHOLD = 0.1f;
 
+    private static final String KEY_BACKGROUND_ANIMATION_TOGGLED_ON =
+            "background_animation_toggled_on";
+
     private final AnimatedFloat mSwipeProgress = new AnimatedFloat(this::onSwipeProgressUpdate);
 
     private final InvariantDeviceProfile.OnIDPChangeListener mOnIDPChangeListener =
@@ -124,6 +128,9 @@
 
     private AnimatorPlaybackController mLauncherStartAnim = null;
 
+    // Auto play background animation by default
+    private boolean mBackgroundAnimationToggledOn = true;
+
     private TextView mHintView;
 
     private final OverviewChangeListener mOverviewChangeListener = this::onOverviewTargetChange;
@@ -200,6 +207,15 @@
                         LOTTIE_TERTIARY_COLOR_TOKEN, R.color.all_set_bg_tertiary),
                 getTheme());
 
+        mBackgroundAnimationToggledOn = savedInstanceState == null
+                || savedInstanceState.getBoolean(KEY_BACKGROUND_ANIMATION_TOGGLED_ON, true);
+        // The animated background is behind a scroll view, which intercepts all input.
+        // However, the content view also covers the full screen
+        requireViewById(R.id.content).setOnClickListener(v -> {
+            mBackgroundAnimationToggledOn = !mBackgroundAnimationToggledOn;
+            maybeResumeOrPauseBackgroundAnimation();
+        });
+
         setUpBackgroundAnimation(getDP().isTablet);
         getIDP().addOnChangeListener(mOnIDPChangeListener);
 
@@ -208,6 +224,12 @@
         ActivityPreloadUtil.preloadOverviewForSUWAllSet(this);
     }
 
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(KEY_BACKGROUND_ANIMATION_TOGGLED_ON, mBackgroundAnimationToggledOn);
+    }
+
     private InvariantDeviceProfile getIDP() {
         return LauncherAppState.getInstance(this).getInvariantDeviceProfile();
     }
@@ -368,8 +390,10 @@
 
     private void maybeResumeOrPauseBackgroundAnimation() {
         boolean shouldPlayAnimation =
-                getContentViewAlphaForSwipeProgress() > ANIMATION_PAUSE_ALPHA_THRESHOLD
-                        && isResumed();
+                !RemoveAnimationSettingsTracker.INSTANCE.get(this).isRemoveAnimationEnabled()
+                        && getContentViewAlphaForSwipeProgress() > ANIMATION_PAUSE_ALPHA_THRESHOLD
+                        && isResumed()
+                        && mBackgroundAnimationToggledOn;
         if (mAnimatedBackground.isAnimating() && !shouldPlayAnimation) {
             mAnimatedBackground.pauseAnimation();
         } else if (!mAnimatedBackground.isAnimating() && shouldPlayAnimation) {
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 0fc95e2..e73fb3b 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -57,6 +57,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.taskbar.TypefaceUtils;
 import com.android.launcher3.views.ClipIconView;
 import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
 import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
@@ -177,6 +178,7 @@
 
         mFeedbackTitleView.setText(getIntroductionTitle());
         mFeedbackSubtitleView.setText(getIntroductionSubtitle());
+        setTitleTypefaces();
 
         mExitingAppView.setClipToOutline(true);
         mExitingAppView.setOutlineProvider(new ViewOutlineProvider() {
@@ -434,6 +436,10 @@
 
         if (isGestureSuccessful) {
             if (mTutorialFragment.isAtFinalStep()) {
+                TypefaceUtils.setTypeface(
+                        mDoneButton,
+                        TypefaceUtils.FONT_FAMILY_LABEL_LARGE_BASELINE
+                );
                 showActionButton();
             }
 
@@ -458,7 +464,8 @@
         pauseAndHideLottieAnimation();
         mCheckmarkAnimation.setVisibility(View.VISIBLE);
         mCheckmarkAnimation.playAnimation();
-        mFeedbackTitleView.setTextAppearance(mContext, getSuccessTitleTextAppearance());
+        mFeedbackTitleView.setTextAppearance(getSuccessTitleTextAppearance());
+        setTitleTypefaces();
     }
 
     public boolean isGestureCompleted() {
@@ -513,8 +520,10 @@
         updateDrawables();
         updateLayout();
 
-        mFeedbackTitleView.setTextAppearance(mContext, getTitleTextAppearance());
-        mDoneButton.setTextAppearance(mContext, getDoneButtonTextAppearance());
+        mFeedbackTitleView.setTextAppearance(getTitleTextAppearance());
+        mDoneButton.setTextAppearance(getDoneButtonTextAppearance());
+
+        setTitleTypefaces();
         mDoneButton.getBackground().setTint(getDoneButtonColor());
         mCheckmarkAnimation.setAnimation(mTutorialFragment.isAtFinalStep()
                 ? R.raw.checkmark_animation_end
@@ -533,6 +542,21 @@
         }
     }
 
+    /**
+     * Apply expressive typefaces to the feedback title and subtitle views.
+     */
+    private void setTitleTypefaces() {
+        TypefaceUtils.setTypeface(
+                mFeedbackTitleView,
+                mTutorialFragment.isLargeScreen()
+                        ? TypefaceUtils.FONT_FAMILY_DISPLAY_MEDIUM_EMPHASIZED
+                        : TypefaceUtils.FONT_FAMILY_DISPLAY_SMALL_EMPHASIZED);
+        TypefaceUtils.setTypeface(
+                mFeedbackSubtitleView,
+                TypefaceUtils.FONT_FAMILY_BODY_LARGE_BASELINE
+        );
+    }
+
     protected void resetViewsForBackGesture() {
         mFakeTaskView.setVisibility(View.VISIBLE);
         mFakeTaskView.setBackgroundColor(getFakeTaskViewColor());
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index 59ea8fa..17f861d 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -577,7 +577,8 @@
         isRtl: Boolean,
         deviceProfile: DeviceProfile,
         splitConfig: SplitBounds,
-        inSplitSelection: Boolean
+        inSplitSelection: Boolean,
+        oneIconHiddenDueToSmallWidth: Boolean,
     ) {
         val spaceAboveSnapshot = deviceProfile.overviewTaskThumbnailTopMarginPx
         val totalThumbnailHeight = groupedTaskViewHeight - spaceAboveSnapshot
@@ -590,7 +591,8 @@
                 totalThumbnailHeight,
                 isRtl,
                 deviceProfile.overviewTaskMarginPx,
-                dividerBar
+                dividerBar,
+                oneIconHiddenDueToSmallWidth,
             )
 
         updateSplitIconsPosition(primaryIconView, topLeftY, isRtl)
@@ -647,6 +649,7 @@
         isRtl: Boolean,
         overviewTaskMarginPx: Int,
         dividerSize: Int,
+        oneIconHiddenDueToSmallWidth: Boolean,
     ): SplitIconPositions {
         return if (Flags.enableOverviewIconMenu()) {
             if (isRtl) {
@@ -655,11 +658,21 @@
                 SplitIconPositions(0, primarySnapshotHeight + dividerSize)
             }
         } else {
-            val topLeftY = primarySnapshotHeight + overviewTaskMarginPx
-            SplitIconPositions(
-                topLeftY = topLeftY,
-                bottomRightY = topLeftY + dividerSize + taskIconHeight
-            )
+            if (oneIconHiddenDueToSmallWidth) {
+                // Center both icons
+                val centerY = primarySnapshotHeight + overviewTaskMarginPx +
+                        ((taskIconHeight + dividerSize) / 2)
+                SplitIconPositions(
+                    topLeftY = centerY,
+                    bottomRightY = centerY,
+                )
+            } else {
+                val topLeftY = primarySnapshotHeight + overviewTaskMarginPx
+                SplitIconPositions(
+                    topLeftY = topLeftY,
+                    bottomRightY = topLeftY + dividerSize + taskIconHeight,
+                )
+            }
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
index d9ad7ce..c4e82d6 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
@@ -671,7 +671,8 @@
     public void setSplitIconParams(View primaryIconView, View secondaryIconView,
             int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
             int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
-            DeviceProfile deviceProfile, SplitBounds splitConfig, boolean inSplitSelection) {
+            DeviceProfile deviceProfile, SplitBounds splitConfig, boolean inSplitSelection,
+            boolean oneIconHiddenDueToSmallWidth) {
         FrameLayout.LayoutParams primaryIconParams =
                 (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
         FrameLayout.LayoutParams secondaryIconParams = enableOverviewIconMenu()
@@ -726,16 +727,30 @@
                 secondaryIconParams.gravity = TOP | (isRtl ? END : START);
                 if (!inSplitSelection) {
                     if (splitConfig.initiatedFromSeascape) {
-                        // if the split was initiated from seascape,
-                        // the task on the right (secondary) is slightly larger
-                        primaryIconView.setTranslationX(bottomToMidpointOffset - taskIconHeight);
-                        secondaryIconView.setTranslationX(bottomToMidpointOffset);
+                        if (oneIconHiddenDueToSmallWidth) {
+                            // Center both icons
+                            float centerX = bottomToMidpointOffset - (taskIconHeight / 2f);
+                            primaryIconView.setTranslationX(centerX);
+                            secondaryIconView.setTranslationX(centerX);
+                        } else {
+                            // the task on the right (secondary) is slightly larger
+                            primaryIconView.setTranslationX(
+                                    bottomToMidpointOffset - taskIconHeight);
+                            secondaryIconView.setTranslationX(bottomToMidpointOffset);
+                        }
                     } else {
-                        // if not,
-                        // the task on the left (primary) is slightly larger
-                        primaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset
-                                - taskIconHeight);
-                        secondaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset);
+                        if (oneIconHiddenDueToSmallWidth) {
+                            // Center both icons
+                            float centerX =
+                                    bottomToMidpointOffset + insetOffset - (taskIconHeight / 2f);
+                            primaryIconView.setTranslationX(centerX);
+                            secondaryIconView.setTranslationX(centerX);
+                        } else {
+                            // the task on the left (primary) is slightly larger
+                            primaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset
+                                    - taskIconHeight);
+                            secondaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset);
+                        }
                     }
                 }
             } else {
@@ -743,16 +758,30 @@
                 secondaryIconParams.gravity = TOP | (isRtl ? START : END);
                 if (!inSplitSelection) {
                     if (!splitConfig.initiatedFromSeascape) {
-                        // if the split was initiated from landscape,
-                        // the task on the left (primary) is slightly larger
-                        primaryIconView.setTranslationX(-bottomToMidpointOffset);
-                        secondaryIconView.setTranslationX(-bottomToMidpointOffset + taskIconHeight);
+                        if (oneIconHiddenDueToSmallWidth) {
+                            // Center both icons
+                            float centerX = -bottomToMidpointOffset + (taskIconHeight / 2f);
+                            primaryIconView.setTranslationX(centerX);
+                            secondaryIconView.setTranslationX(centerX);
+                        } else {
+                            // the task on the left (primary) is slightly larger
+                            primaryIconView.setTranslationX(-bottomToMidpointOffset);
+                            secondaryIconView.setTranslationX(
+                                    -bottomToMidpointOffset + taskIconHeight);
+                        }
                     } else {
-                        // if not,
-                        // the task on the right (secondary) is slightly larger
-                        primaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset);
-                        secondaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset
-                                + taskIconHeight);
+                        if (oneIconHiddenDueToSmallWidth) {
+                            // Center both icons
+                            float centerX =
+                                    -bottomToMidpointOffset - insetOffset + (taskIconHeight / 2f);
+                            primaryIconView.setTranslationX(centerX);
+                            secondaryIconView.setTranslationX(centerX);
+                        } else {
+                            // the task on the right (secondary) is slightly larger
+                            primaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset);
+                            secondaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset
+                                    + taskIconHeight);
+                        }
                     }
                 }
             }
@@ -760,9 +789,15 @@
             primaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
             secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
             if (!inSplitSelection) {
-                // shifts icon half a width left (height is used here since icons are square)
-                primaryIconView.setTranslationX(-(taskIconHeight / 2f));
-                secondaryIconView.setTranslationX(taskIconHeight / 2f);
+                if (oneIconHiddenDueToSmallWidth) {
+                    // Center both icons
+                    primaryIconView.setTranslationX(0);
+                    secondaryIconView.setTranslationX(0);
+                } else {
+                    // shifts icon half a width left (height is used here since icons are square)
+                    primaryIconView.setTranslationX(-(taskIconHeight / 2f));
+                    secondaryIconView.setTranslationX(taskIconHeight / 2f);
+                }
             }
         }
         if (!enableOverviewIconMenu() && !inSplitSelection) {
diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
index 78f9a0a..9b3c467 100644
--- a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
@@ -237,7 +237,8 @@
         isRtl: Boolean,
         deviceProfile: DeviceProfile,
         splitConfig: SplitConfigurationOptions.SplitBounds,
-        inSplitSelection: Boolean
+        inSplitSelection: Boolean,
+        oneIconHiddenDueToSmallWidth: Boolean,
     )
 
     /*
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index 9bfa2bf..0cb983d 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -353,17 +353,18 @@
         isRtl: Boolean,
         overviewTaskMarginPx: Int,
         dividerSize: Int,
+        oneIconHiddenDueToSmallWidth: Boolean,
     ): SplitIconPositions {
         return if (Flags.enableOverviewIconMenu()) {
             if (isRtl) {
                 SplitIconPositions(
                     topLeftY = totalThumbnailHeight - primarySnapshotHeight,
-                    bottomRightY = 0
+                    bottomRightY = 0,
                 )
             } else {
                 SplitIconPositions(
                     topLeftY = 0,
-                    bottomRightY = -(primarySnapshotHeight + dividerSize)
+                    bottomRightY = -(primarySnapshotHeight + dividerSize),
                 )
             }
         } else {
@@ -372,10 +373,19 @@
             // from the bottom to the almost-center of the screen using the bottom margin.
             // The primary snapshot is placed at the bottom, thus we translate the icons using
             // the size of the primary snapshot minus the icon size for the top-left icon.
-            SplitIconPositions(
-                topLeftY = primarySnapshotHeight - taskIconHeight,
-                bottomRightY = primarySnapshotHeight + dividerSize
-            )
+            if (oneIconHiddenDueToSmallWidth) {
+                // Center both icons
+                val centerY = primarySnapshotHeight + ((dividerSize - taskIconHeight) / 2)
+                SplitIconPositions(
+                    topLeftY = centerY,
+                    bottomRightY = centerY,
+                )
+            } else {
+                SplitIconPositions(
+                    topLeftY = primarySnapshotHeight - taskIconHeight,
+                    bottomRightY = primarySnapshotHeight + dividerSize,
+                )
+            }
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index e91073a..0edbacc 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -181,8 +181,10 @@
     private fun resetViews() {
         liveTileView.isInvisible = true
         thumbnailView.isInvisible = true
+        thumbnailView.setImageBitmap(null)
         splashBackground.alpha = 0f
         splashIcon.alpha = 0f
+        splashIcon.setImageDrawable(null)
         scrimView.alpha = 0f
         setBackgroundColor(Color.BLACK)
         taskThumbnailViewHeader?.isInvisible = true
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 12ca257..a2856a6 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -81,8 +81,10 @@
 
     public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity,
             boolean animateOverviewScrim, @Nullable View ignoredView, boolean staggerWorkspace) {
+        boolean isPinnedTaskbarAndNotInDesktopMode = DisplayController.isPinnedTaskbar(launcher)
+                && !DisplayController.isInDesktopMode(launcher);
         mTaskbarDurationInMs = QuickstepTransitionManager.getTaskbarToHomeDuration(
-                DisplayController.isPinnedTaskbar(launcher));
+                isPinnedTaskbarAndNotInDesktopMode);
         prepareToAnimate(launcher, animateOverviewScrim);
 
         mIgnoredView = ignoredView;
diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
index 9943770..244c3b2 100644
--- a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
+++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
@@ -20,10 +20,12 @@
 import android.graphics.Canvas
 import android.graphics.Rect
 import android.util.AttributeSet
+import android.view.View
 import android.widget.ImageButton
 import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
 import com.android.launcher3.R
 import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.MultiValueAlpha
 import com.android.quickstep.util.BorderAnimator
 import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
 
@@ -34,6 +36,22 @@
 class AddDesktopButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
     ImageButton(context, attrs) {
 
+    private enum class Alpha {
+        CONTENT,
+        VISIBILITY,
+    }
+
+    private val addDeskButtonAlpha = MultiValueAlpha(this, Alpha.entries.size)
+
+    var contentAlpha
+        set(value) {
+            addDeskButtonAlpha.get(Alpha.CONTENT.ordinal).value = value
+        }
+        get() = addDeskButtonAlpha.get(Alpha.CONTENT.ordinal).value
+
+    val visibilityAlphaProperty: MultiPropertyFactory<View>.MultiProperty
+        get() = addDeskButtonAlpha.get(Alpha.VISIBILITY.ordinal)
+
     private enum class TranslationX {
         GRID,
         OFFSET,
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 25011d7..a8eee0a 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -187,14 +187,30 @@
         val taskIconHeight = deviceProfile.overviewTaskIconSizePx
         val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
         val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
+        var oneIconHiddenDueToSmallWidth = false
 
         if (enableFlexibleTwoAppSplit()) {
-            val topLeftTaskPercent = splitBoundsConfig.leftTopTaskPercent
-            val bottomRightTaskPercent = splitBoundsConfig.rightBottomTaskPercent
-            val hideTopLeftIcon = topLeftTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON
-            val hideBottomRightIcon = bottomRightTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON
-            leftTopTaskContainer.iconView.setFlexSplitAlpha(if (hideTopLeftIcon) 0f else 1f)
-            rightBottomTaskContainer.iconView.setFlexSplitAlpha(if (hideBottomRightIcon) 0f else 1f)
+            // Update values for both icons' setFlexSplitAlpha. Mainly, we want to hide an icon if
+            // its app tile is too small. But we also have to set the alphas back if we go to
+            // split selection.
+            val hideLeftTopIcon: Boolean
+            val hideRightBottomIcon: Boolean
+            if (inSplitSelection) {
+                hideLeftTopIcon =
+                    getThisTaskCurrentlyInSplitSelection() == splitBoundsConfig.leftTopTaskId
+                hideRightBottomIcon =
+                    getThisTaskCurrentlyInSplitSelection() == splitBoundsConfig.rightBottomTaskId
+            } else {
+                hideLeftTopIcon = splitBoundsConfig.leftTopTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON
+                hideRightBottomIcon =
+                    splitBoundsConfig.rightBottomTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON
+                if (hideLeftTopIcon || hideRightBottomIcon) {
+                    oneIconHiddenDueToSmallWidth = true
+                }
+            }
+
+            leftTopTaskContainer.iconView.setFlexSplitAlpha(if (hideLeftTopIcon) 0f else 1f)
+            rightBottomTaskContainer.iconView.setFlexSplitAlpha(if (hideRightBottomIcon) 0f else 1f)
         }
 
         if (enableOverviewIconMenu()) {
@@ -217,6 +233,7 @@
                 deviceProfile,
                 splitBoundsConfig,
                 inSplitSelection,
+                oneIconHiddenDueToSmallWidth,
             )
         } else {
             pagedOrientationHandler.setSplitIconParams(
@@ -231,6 +248,7 @@
                 deviceProfile,
                 splitBoundsConfig,
                 inSplitSelection,
+                oneIconHiddenDueToSmallWidth,
             )
         }
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index f53583a..6b91df1 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3908,6 +3908,22 @@
                 // the only invariant point in landscape split screen.
                 snapToLastTask = true;
             }
+            if (mUtils.getGridTaskCount() == 1 && dismissedTaskView.isGridTask()) {
+                TaskView lastLargeTile = mUtils.getLastLargeTaskView();
+                if (lastLargeTile != null) {
+                    // Calculate the distance to put last large tile back to middle of the screen.
+                    int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
+                    int lastLargeTileScroll = getScrollForPage(indexOfChild(lastLargeTile));
+                    longGridRowWidthDiff = primaryScroll - lastLargeTileScroll;
+
+                    if (!isClearAllHidden) {
+                        // If ClearAllButton is visible, reduce the distance by scroll difference
+                        // between ClearAllButton and the last task.
+                        longGridRowWidthDiff += getLastTaskScroll(/*clearAllScroll=*/0,
+                                getPagedOrientationHandler().getPrimarySize(mClearAllButton));
+                    }
+                }
+            }
 
             // If we need to animate the grid to compensate the clear all gap, we split the second
             // half of the dismiss pending animation (in which the non-dismissed tasks slide into
@@ -4746,9 +4762,8 @@
         }
         mClearAllButton.setContentAlpha(mContentAlpha);
 
-        // TODO(b/389209338): Handle the visibility of the `mAddDesktopButton`.
         if (mAddDesktopButton != null) {
-            mAddDesktopButton.setAlpha(mContentAlpha);
+            mAddDesktopButton.setContentAlpha(mContentAlpha);
         }
         int alphaInt = Math.round(alpha * 255);
         mEmptyMessagePaint.setAlpha(alphaInt);
@@ -6313,6 +6328,11 @@
         return mClearAllButton;
     }
 
+    @Nullable
+    public AddDesktopButton getAddDeskButton() {
+        return mAddDesktopButton;
+    }
+
     /**
      * @return How many pixels the running task is offset on the currently laid out dominant axis.
      */
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index 8df7430..67318ac 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -85,6 +85,9 @@
     /** Counts [TaskView]s that are large tiles. */
     fun getLargeTileCount(): Int = taskViews.count { it.isLargeTile }
 
+    /** Counts [TaskView]s that are grid tasks. */
+    fun getGridTaskCount(): Int = taskViews.count { it.isGridTask }
+
     /** Returns the first TaskView that should be displayed as a large tile. */
     fun getFirstLargeTaskView(): TaskView? =
         taskViews.firstOrNull {
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 6339c5e..2b9d036 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -109,6 +109,8 @@
         overlay.destroy()
         if (enableRefactorTaskThumbnail()) {
             isThumbnailValid = false
+            thumbnailData = null
+            thumbnailView.onRecycle()
         } else {
             thumbnailViewDeprecated.setShowSplashForSplitSelection(false)
         }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index 2cd09cc..3a27bb1 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -333,10 +333,12 @@
                 Task(Task.TaskKey(it, 0, Intent(), ComponentName("", ""), 0, 2000))
             }
         recentsModel.updateRecentTasks(listOf(DesktopTask(deskId = 0, tasks)))
-        desktopTaskListener?.onTasksVisibilityChanged(
-            context.virtualDisplay.display.displayId,
-            tasksToAdd,
-        )
+        for (task in 1..tasksToAdd) {
+            desktopTaskListener?.onTasksVisibilityChanged(
+                context.virtualDisplay.display.displayId,
+                task,
+            )
+        }
         runOnMainSync { recentsModel.resolvePendingTaskRequests() }
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index 002c988..8376bc1 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -959,6 +959,8 @@
     private fun setInDesktopMode(inDesktopMode: Boolean) {
         whenever(taskbarControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar())
             .thenReturn(inDesktopMode)
+        whenever(taskbarControllers.taskbarDesktopModeController.isInDesktopMode)
+            .thenReturn(inDesktopMode)
     }
 
     private fun createItemInfo(
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
index ea52842..0570c26 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
@@ -62,6 +62,7 @@
             isRTL,
             OVERVIEW_TASK_MARGIN_PX,
             DIVIDER_SIZE_PX,
+            oneIconHiddenDueToSmallWidth = false,
         )
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
index 2bc182c..3788688 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
@@ -62,6 +62,7 @@
             isRTL,
             OVERVIEW_TASK_MARGIN_PX,
             DIVIDER_SIZE_PX,
+            oneIconHiddenDueToSmallWidth = false,
         )
     }
 
diff --git a/res/drawable/private_space_install_app_icon.xml b/res/drawable/private_space_install_app_icon.xml
index cfec2b1..1e7fe43 100644
--- a/res/drawable/private_space_install_app_icon.xml
+++ b/res/drawable/private_space_install_app_icon.xml
@@ -13,19 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="60dp"
-    android:height="60dp"
-    android:viewportWidth="60"
-    android:viewportHeight="60">
-    <group>
-        <clip-path
-            android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z" />
-        <path
-            android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z"
-            android:fillColor="@color/material_color_surface_container_lowest" />
-        <path
-            android:pathData="M29 31h-6v-2h6v-6h2v6h6v2h-6v6h-2v-6Z"
-            android:fillColor="@color/material_color_on_surface" />
-    </group>
-</vector>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/material_color_surface_container_lowest"/>
+    <foreground android:drawable="@drawable/private_space_install_app_icon_foreground" />
+</adaptive-icon>
diff --git a/res/drawable/private_space_install_app_icon_foreground.xml b/res/drawable/private_space_install_app_icon_foreground.xml
new file mode 100644
index 0000000..d55abe7
--- /dev/null
+++ b/res/drawable/private_space_install_app_icon_foreground.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2025 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="60dp"
+    android:height="60dp"
+    android:viewportWidth="60"
+    android:viewportHeight="60">
+    <path
+        android:pathData="M29 31h-6v-2h6v-6h2v6h6v2h-6v6h-2v-6Z"
+        android:fillColor="@color/material_color_on_surface" />
+</vector>
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 8c6555e..78ad04b 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -70,6 +70,7 @@
     public static final int WORKSPACE_PAGE_INDICATOR = 1 << 5;
     public static final int SPLIT_PLACHOLDER_VIEW = 1 << 6;
     public static final int FLOATING_SEARCH_BAR = 1 << 7;
+    public static final int ADD_DESK_BUTTON = 1 << 8;
 
     // Flag indicating workspace has multiple pages visible.
     public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 5072e37..d6abb56 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.app.animation.Interpolators.SCROLL;
+import static com.android.launcher3.RemoveAnimationSettingsTracker.WINDOW_ANIMATION_SCALE_URI;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
 import static com.android.launcher3.testing.shared.TestProtocol.SCROLL_FINISHED_MESSAGE;
@@ -33,7 +34,6 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.InputDevice;
@@ -1756,8 +1756,8 @@
         }
 
         if (FeatureFlags.IS_STUDIO_BUILD && !Utilities.isRunningInTestHarness()) {
-            duration *= Settings.Global.getFloat(getContext().getContentResolver(),
-                    Settings.Global.WINDOW_ANIMATION_SCALE, 1);
+            duration *= RemoveAnimationSettingsTracker.INSTANCE.get(getContext()).getValue(
+                    WINDOW_ANIMATION_SCALE_URI);
         }
 
         whichPage = validateNewPage(whichPage);
diff --git a/src/com/android/launcher3/RemoveAnimationSettingsTracker.kt b/src/com/android/launcher3/RemoveAnimationSettingsTracker.kt
new file mode 100644
index 0000000..dbc04f1
--- /dev/null
+++ b/src/com/android/launcher3/RemoveAnimationSettingsTracker.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3
+
+import android.content.Context
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.provider.Settings.Global.ANIMATOR_DURATION_SCALE
+import android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE
+import android.provider.Settings.Global.WINDOW_ANIMATION_SCALE
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+
+/** Tracker Class for when user turns on/off remove animation setting. */
+@LauncherAppSingleton
+class RemoveAnimationSettingsTracker
+@Inject
+constructor(@ApplicationContext val context: Context, tracker: DaggerSingletonTracker) :
+    ContentObserver(Handler(Looper.getMainLooper())) {
+
+    private val contentResolver = context.contentResolver
+
+    /** Caches the last seen value for registered keys. */
+    private val cache: MutableMap<Uri, Float> = ConcurrentHashMap()
+
+    init {
+        UI_HELPER_EXECUTOR.execute {
+            contentResolver.registerContentObserver(WINDOW_ANIMATION_SCALE_URI, false, this)
+            contentResolver.registerContentObserver(TRANSITION_ANIMATION_SCALE_URI, false, this)
+            contentResolver.registerContentObserver(ANIMATOR_DURATION_SCALE_URI, false, this)
+        }
+
+        tracker.addCloseable {
+            UI_HELPER_EXECUTOR.execute { contentResolver.unregisterContentObserver(this) }
+        }
+    }
+
+    /**
+     * Returns the value for this classes key from the cache. If not in cache, will call
+     * [updateValue] to fetch.
+     */
+    fun getValue(uri: Uri): Float {
+        return getValue(uri, 1f)
+    }
+
+    /**
+     * Returns the value for this classes key from the cache. If not in cache, will call
+     * [getValueFromSettingsGlobal] to fetch.
+     */
+    private fun getValue(uri: Uri, defaultValue: Float): Float {
+        return cache.computeIfAbsent(uri) { getValueFromSettingsGlobal(uri, defaultValue) }
+    }
+
+    /** Returns if user has opted into having no animation on their device. */
+    fun isRemoveAnimationEnabled(): Boolean {
+        return getValue(WINDOW_ANIMATION_SCALE_URI) == 0f &&
+            getValue(TRANSITION_ANIMATION_SCALE_URI) == 0f &&
+            getValue(ANIMATOR_DURATION_SCALE_URI) == 0f
+    }
+
+    override fun onChange(selfChange: Boolean, uri: Uri?) {
+        if (uri == null) return
+        updateValue(uri)
+    }
+
+    private fun getValueFromSettingsGlobal(uri: Uri, defaultValue: Float = 1f): Float {
+        return Settings.Global.getFloat(contentResolver, uri.lastPathSegment, defaultValue)
+    }
+
+    private fun updateValue(uri: Uri, defaultValue: Float = 1f) {
+        val newValue = getValueFromSettingsGlobal(uri, defaultValue)
+        cache[uri] = newValue
+    }
+
+    companion object {
+        @JvmField
+        val INSTANCE =
+            DaggerSingletonObject(LauncherAppComponent::getRemoveAnimationSettingsTracker)
+        @JvmField
+        val WINDOW_ANIMATION_SCALE_URI: Uri = Settings.Global.getUriFor(WINDOW_ANIMATION_SCALE)
+        @JvmField
+        val TRANSITION_ANIMATION_SCALE_URI: Uri =
+            Settings.Global.getUriFor(TRANSITION_ANIMATION_SCALE)
+        @JvmField
+        val ANIMATOR_DURATION_SCALE_URI: Uri = Settings.Global.getUriFor(ANIMATOR_DURATION_SCALE)
+    }
+}
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 249a214..150761f 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -23,6 +23,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.RemoveAnimationSettingsTracker;
 import com.android.launcher3.graphics.GridCustomizationsProxy;
 import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.icons.LauncherIcons.IconPool;
@@ -80,8 +81,8 @@
     LockedUserState getLockedUserState();
     InvariantDeviceProfile getIDP();
     IconPool getIconPool();
+    RemoveAnimationSettingsTracker getRemoveAnimationSettingsTracker();
     LauncherAppState getLauncherAppState();
-
     GridCustomizationsProxy getGridCustomizationsProxy();
 
     /** Builder for LauncherBaseAppComponent. */
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProxy.java b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
index 062c753..40863c4 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
@@ -24,11 +24,9 @@
 
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder.DeathRecipient;
@@ -297,12 +295,6 @@
 
     @Override
     public Bundle call(@NonNull String method, String arg, Bundle extras) {
-        if (mContext.checkPermission("android.permission.BIND_WALLPAPER",
-                Binder.getCallingPid(), Binder.getCallingUid())
-                != PackageManager.PERMISSION_GRANTED) {
-            return null;
-        }
-
         if (METHOD_GET_PREVIEW.equals(method)) {
             return getPreview(extras);
         } else {
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 4dd9c5b..8af18f5 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -64,7 +64,6 @@
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.LoaderTask;
 import com.android.launcher3.model.ModelDbController;
-import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Themes;
@@ -336,16 +335,6 @@
             // Start the migration
             PreviewContext previewContext =
                     new PreviewContext(inflationContext, mGridName, mShapeKey);
-            // Copy existing data to preview DB
-            LauncherDbUtils.copyTable(LauncherAppState.getInstance(mContext)
-                            .getModel().getModelDbController().getDb(),
-                    TABLE_NAME,
-                    LauncherAppState.getInstance(previewContext)
-                            .getModel().getModelDbController().getDb(),
-                    TABLE_NAME,
-                    mContext);
-            LauncherAppState.getInstance(previewContext)
-                    .getModel().getModelDbController().clearEmptyDbFlag();
 
             BgDataModel bgModel = new BgDataModel();
             new LoaderTask(
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index b8f6424..119a6b1 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.LooperExecutor.CALLER_ICON_CACHE;
 import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY;
 
 import static java.util.stream.Collectors.groupingBy;
@@ -35,7 +36,6 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteException;
 import android.os.Looper;
-import android.os.Process;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -204,7 +204,7 @@
         Runnable endRunnable;
         if (Looper.myLooper() == Looper.getMainLooper()) {
             if (mPendingIconRequestCount <= 0) {
-                MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
+                MODEL_EXECUTOR.elevatePriority(CALLER_ICON_CACHE);
             }
             mPendingIconRequestCount++;
             endRunnable = this::onIconRequestEnd;
@@ -221,7 +221,7 @@
     private void onIconRequestEnd() {
         mPendingIconRequestCount--;
         if (mPendingIconRequestCount <= 0) {
-            MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+            MODEL_EXECUTOR.restorePriority(CALLER_ICON_CACHE);
         }
     }
 
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 3ee8b87..a2ca6b6 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -28,7 +28,6 @@
 
 import static java.util.Collections.emptyList;
 
-import android.os.Process;
 import android.os.Trace;
 import android.util.Log;
 import android.util.Pair;
@@ -352,14 +351,8 @@
                 onCompleteSignal.executeAllAndDestroy();
             }
 
-            executeCallbacksTask(
-                    c -> {
-                        if (!enableWorkspaceInflation()) {
-                            MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-                        }
-                        c.onInitialBindComplete(currentScreenIds, pendingTasks, onCompleteSignal,
-                                workspaceItemCount, isBindSync);
-                    }, mUiExecutor);
+            executeCallbacksTask(c -> c.onInitialBindComplete(currentScreenIds, pendingTasks,
+                    onCompleteSignal, workspaceItemCount, isBindSync), mUiExecutor);
         }
 
         private void setupPendingBind(
@@ -369,12 +362,8 @@
             executeCallbacksTask(c -> c.bindStringCache(cacheClone), pendingExecutor);
 
             executeCallbacksTask(c -> c.finishBindingItems(currentScreenIds), pendingExecutor);
-            pendingExecutor.execute(
-                    () -> {
-                        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
-                        ItemInstallQueue.INSTANCE.get(mApp.getContext())
-                                .resumeModelPush(FLAG_LOADER_RUNNING);
-                    });
+            pendingExecutor.execute(() -> ItemInstallQueue.INSTANCE.get(mApp.getContext())
+                    .resumeModelPush(FLAG_LOADER_RUNNING));
         }
 
         /**
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 6f12c97..d06f541 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -33,7 +33,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 
 import java.util.Locale;
 import java.util.Objects;
@@ -103,9 +102,6 @@
      * Stores the device state to shared preferences
      */
     public void writeToPrefs(Context context) {
-        if (context instanceof SandboxContext) {
-            return;
-        }
         LauncherPrefs.get(context).put(
                 WORKSPACE_SIZE.to(mGridSizeString),
                 HOTSEAT_COUNT.to(mNumHotseat),
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index c1ee69b..d44b289 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -36,6 +36,7 @@
 import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.LooperExecutor.CALLER_LOADER_TASK;
 import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
 
 import android.appwidget.AppWidgetProviderInfo;
@@ -251,6 +252,7 @@
         }
 
         TraceHelper.INSTANCE.beginSection(TAG);
+        MODEL_EXECUTOR.elevatePriority(CALLER_LOADER_TASK);
         LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
         mIsRestoreFromBackup =
                 LauncherPrefs.get(mApp.getContext()).get(IS_FIRST_LOAD_AFTER_RESTORE);
@@ -403,6 +405,7 @@
             memoryLogger.printLogs();
             throw e;
         }
+        MODEL_EXECUTOR.restorePriority(CALLER_LOADER_TASK);
         TraceHelper.INSTANCE.endSection();
     }
 
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 3a55aa7..feae632 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -80,7 +80,6 @@
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.util.IOUtils;
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 import com.android.launcher3.util.Partner;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 
@@ -143,14 +142,11 @@
     }
 
     protected DatabaseHelper createDatabaseHelper(boolean forMigration, String dbFile) {
-        boolean isSandbox = mContext instanceof SandboxContext;
-        String dbName = isSandbox ? null : dbFile;
-
         // Set the flag for empty DB
         Runnable onEmptyDbCreateCallback = forMigration ? () -> { }
-                : () -> LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey(dbName).to(true));
+                : () -> LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey(dbFile).to(true));
 
-        DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbName,
+        DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbFile,
                 this::getSerialNumberForUser, onEmptyDbCreateCallback);
         // Table creation sometimes fails silently, which leads to a crash loop.
         // This way, we will try to create a table every time after crash, so the device
@@ -380,8 +376,7 @@
                 .filter(dbName -> mContext.getDatabasePath(dbName).exists())
                 .collect(Collectors.toList());
 
-        mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
-                : createDatabaseHelper(true, new DeviceGridState(idp).getDbFile());
+        mOpenHelper = createDatabaseHelper(true, new DeviceGridState(idp).getDbFile());
         try {
             // This is the current grid we have, given by the mContext
             DeviceGridState srcDeviceState = new DeviceGridState(mContext);
@@ -462,8 +457,7 @@
         List<String> existingDBs = LauncherFiles.GRID_DB_FILES.stream()
                 .filter(dbName -> mContext.getDatabasePath(dbName).exists())
                 .collect(Collectors.toList());
-        mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
-                : createDatabaseHelper(true /* forMigration */, targetDbName);
+        mOpenHelper = createDatabaseHelper(true /* forMigration */, targetDbName);
         try {
             // This is the current grid we have, given by the mContext
             DeviceGridState srcDeviceState = new DeviceGridState(mContext);
@@ -763,10 +757,6 @@
      * string will be "EMPTY_DATABASE_CREATED@minimal.db".
      */
     private ConstantItem<Boolean> getEmptyDbCreatedKey(String dbName) {
-        if (mContext instanceof SandboxContext) {
-            return LauncherPrefs.nonRestorableItem(EMPTY_DATABASE_CREATED,
-                    false /* default value */, EncryptionType.ENCRYPTED);
-        }
         String key = TextUtils.equals(dbName, LauncherFiles.LAUNCHER_DB)
                 ? EMPTY_DATABASE_CREATED : EMPTY_DATABASE_CREATED + "@" + dbName;
         return LauncherPrefs.backedUpItem(key, false /* default value */, EncryptionType.ENCRYPTED);
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index d1eceb9..3cdb250 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -239,25 +239,31 @@
                             boolean isTargetValid = !cn.getClassName().equals(
                                     IconCache.EMPTY_CLASS_NAME);
                             if (itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
+                                int requestQuery = ShortcutRequest.PINNED;
+                                if (Flags.restoreArchivedShortcuts()) {
+                                    // Avoid race condition where shortcut service has no record of
+                                    // unarchived shortcut being pinned after restore.
+                                    // Launcher should be source-of-truth for if shortcut is pinned.
+                                    requestQuery = ShortcutRequest.ALL;
+                                }
                                 List<ShortcutInfo> shortcut =
                                         new ShortcutRequest(context, mUser)
                                                 .forPackage(cn.getPackageName(),
                                                         itemInfo.getDeepShortcutId())
-                                                .query(ShortcutRequest.PINNED);
-                                if (shortcut.isEmpty()
-                                        && !(Flags.restoreArchivedShortcuts()
-                                            && !itemInfo.isArchived())
-                                ) {
+                                                .query(requestQuery);
+                                if (shortcut.isEmpty()) {
                                     isTargetValid = false;
                                     if (DEBUG) {
-                                        Log.d(TAG, "Pinned Shortcut not found for updated"
-                                                + " package=" + itemInfo.getTargetPackage());
-                                    }
-                                } else if (!shortcut.isEmpty()) {
-                                    if (DEBUG) {
-                                        Log.d(TAG, "Found pinned shortcut for updated"
+                                        Log.d(TAG, "Shortcut not found for updated"
                                                 + " package=" + itemInfo.getTargetPackage()
-                                                + ", isTargetValid=" + isTargetValid);
+                                                + ", isArchived=" + itemInfo.isArchived());
+                                    }
+                                } else {
+                                    if (DEBUG) {
+                                        Log.d(TAG, "Found shortcut for updated"
+                                                + " package=" + itemInfo.getTargetPackage()
+                                                + ", isTargetValid=" + isTargetValid
+                                                + ", isArchived=" + itemInfo.isArchived());
                                     }
                                     itemInfo.updateFromDeepShortcutInfo(shortcut.get(0), context);
                                     infoUpdated = true;
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index c20d655..b1653d0 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.secondarydisplay;
 
 import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme;
+import static com.android.window.flags.Flags.enableTaskbarConnectedDisplays;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -124,6 +125,10 @@
         mDragLayer = findViewById(R.id.drag_layer);
         mAppsView = findViewById(R.id.apps_view);
         mAppsButton = findViewById(R.id.all_apps_button);
+        // TODO (b/391965805): Replace this flag with DesktopExperiences flag.
+        if (enableTaskbarConnectedDisplays()) {
+            mAppsButton.setVisibility(View.INVISIBLE);
+        }
 
         mDragController.addDragListener(this);
         mPopupDataProvider = new PopupDataProvider(
@@ -243,7 +248,9 @@
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     mAppsView.setVisibility(View.INVISIBLE);
-                    mAppsButton.setVisibility(View.VISIBLE);
+                    // TODO (b/391965805): Replace this flag with DesktopExperiences flag.
+                    mAppsButton.setVisibility(
+                            enableTaskbarConnectedDisplays() ? View.INVISIBLE : View.VISIBLE);
                     mAppsView.getSearchUiManager().resetSearch();
                 }
             });
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 376a61e..ceece4d 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -99,10 +99,11 @@
     public static final int CHANGE_NAVIGATION_MODE = 1 << 4;
     public static final int CHANGE_TASKBAR_PINNING = 1 << 5;
     public static final int CHANGE_DESKTOP_MODE = 1 << 6;
+    public static final int CHANGE_SHOW_LOCKED_TASKBAR = 1 << 7;
 
     public static final int CHANGE_ALL = CHANGE_ACTIVE_SCREEN | CHANGE_ROTATION
             | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS | CHANGE_NAVIGATION_MODE
-            | CHANGE_TASKBAR_PINNING | CHANGE_DESKTOP_MODE;
+            | CHANGE_TASKBAR_PINNING | CHANGE_DESKTOP_MODE | CHANGE_SHOW_LOCKED_TASKBAR;
 
     private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
     private static final String TARGET_OVERLAY_PACKAGE = "android";
@@ -212,6 +213,13 @@
     }
 
     /**
+     * Returns whether the taskbar is pinned in gesture navigation mode.
+     */
+    public static boolean isInDesktopMode(Context context) {
+        return INSTANCE.get(context).getInfo().isInDesktopMode();
+    }
+
+    /**
      * Returns whether the taskbar is forced to be pinned when home is visible.
      */
     public static boolean showLockedTaskbarOnHome(Context context) {
@@ -335,6 +343,9 @@
         if (newInfo.mIsInDesktopMode != oldInfo.mIsInDesktopMode) {
             change |= CHANGE_DESKTOP_MODE;
         }
+        if (newInfo.mShowLockedTaskbarOnHome != oldInfo.mShowLockedTaskbarOnHome) {
+            change |= CHANGE_SHOW_LOCKED_TASKBAR;
+        }
 
         if (DEBUG) {
             Log.d(TAG, "handleInfoChange - change: " + getChangeFlagsString(change));
@@ -494,6 +505,13 @@
         }
 
         /**
+         * Returns whether the taskbar is in desktop mode.
+         */
+        public boolean isInDesktopMode() {
+            return mIsInDesktopMode;
+        }
+
+        /**
          * Returns {@code true} if the bounds represent a tablet.
          */
         public boolean isTablet(WindowBounds bounds) {
@@ -575,6 +593,7 @@
         appendFlag(result, change, CHANGE_NAVIGATION_MODE, "CHANGE_NAVIGATION_MODE");
         appendFlag(result, change, CHANGE_TASKBAR_PINNING, "CHANGE_TASKBAR_VARIANT");
         appendFlag(result, change, CHANGE_DESKTOP_MODE, "CHANGE_DESKTOP_MODE");
+        appendFlag(result, change, CHANGE_SHOW_LOCKED_TASKBAR, "CHANGE_SHOW_LOCKED_TASKBAR");
         return result.toString();
     }
 
@@ -592,6 +611,7 @@
         pw.println("  isTaskbarPinned=" + info.mIsTaskbarPinned);
         pw.println("  isTaskbarPinnedInDesktopMode=" + info.mIsTaskbarPinnedInDesktopMode);
         pw.println("  isInDesktopMode=" + info.mIsInDesktopMode);
+        pw.println("  showLockedTaskbarOnHome=" + info.showLockedTaskbarOnHome());
         pw.println("  currentSize=" + info.currentSize);
         info.mPerDisplayBounds.forEach((key, value) -> pw.println(
                 "  perDisplayBounds - " + key + ": " + value));
diff --git a/src/com/android/launcher3/util/LooperExecutor.kt b/src/com/android/launcher3/util/LooperExecutor.kt
index f5ba906..6ff528d 100644
--- a/src/com/android/launcher3/util/LooperExecutor.kt
+++ b/src/com/android/launcher3/util/LooperExecutor.kt
@@ -19,8 +19,11 @@
 import android.os.HandlerThread
 import android.os.Looper
 import android.os.Process
+import android.os.Process.THREAD_PRIORITY_FOREGROUND
+import androidx.annotation.IntDef
 import java.util.concurrent.AbstractExecutorService
 import java.util.concurrent.TimeUnit
+import kotlin.annotation.AnnotationRetention.SOURCE
 
 /** Extension of [AbstractExecutorService] which executed on a provided looper. */
 class LooperExecutor(looper: Looper, private val defaultPriority: Int) : AbstractExecutorService() {
@@ -40,6 +43,8 @@
     val looper: Looper
         get() = handler.looper
 
+    @ElevationCaller private var elevationFlags: Int = 0
+
     override fun execute(runnable: Runnable) {
         if (handler.looper == Looper.myLooper()) {
             runnable.run()
@@ -73,16 +78,31 @@
     }
 
     /**
-     * Set the priority of a thread, based on Linux priorities.
-     *
-     * @param priority Linux priority level, from -20 for highest scheduling priority to 19 for
-     *   lowest scheduling priority.
-     * @see Process.setThreadPriority
+     * Increases the priority of the thread for the [caller]. Multiple calls with same caller are
+     * ignored. The priority is reset once wall callers have restored priority
      */
-    fun setThreadPriority(priority: Int) {
-        Process.setThreadPriority((thread as HandlerThread).threadId, priority)
+    fun elevatePriority(@ElevationCaller caller: Int) {
+        val wasElevated = elevationFlags != 0
+        elevationFlags = elevationFlags.or(caller)
+        if (elevationFlags != 0 && !wasElevated)
+            Process.setThreadPriority(
+                (thread as HandlerThread).threadId,
+                THREAD_PRIORITY_FOREGROUND,
+            )
     }
 
+    /** Restores to default priority if it was previously elevated */
+    fun restorePriority(@ElevationCaller caller: Int) {
+        val wasElevated = elevationFlags != 0
+        elevationFlags = elevationFlags.and(caller.inv())
+        if (elevationFlags == 0 && wasElevated)
+            Process.setThreadPriority((thread as HandlerThread).threadId, defaultPriority)
+    }
+
+    @Retention(SOURCE)
+    @IntDef(value = [CALLER_LOADER_TASK, CALLER_ICON_CACHE], flag = true)
+    annotation class ElevationCaller
+
     companion object {
         /** Utility method to get a started handler thread statically with the provided priority */
         @JvmOverloads
@@ -91,5 +111,8 @@
             name: String,
             priority: Int = Process.THREAD_PRIORITY_DEFAULT,
         ): Looper = HandlerThread(name, priority).apply { start() }.looper
+
+        const val CALLER_LOADER_TASK = 1 shl 0
+        const val CALLER_ICON_CACHE = 1 shl 1
     }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
index aa1451b..0ecb38e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -34,6 +34,7 @@
 import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
 import com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE
 import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
+import com.android.launcher3.util.DisplayController.CHANGE_SHOW_LOCKED_TASKBAR
 import com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener
 import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
@@ -209,8 +210,13 @@
 
         assertTrue(displayController.getInfo().isTransientTaskbar())
         displayController.notifyConfigChange()
+
         verify(displayInfoChangeListener)
-            .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
+            .onDisplayInfoChanged(
+                any(),
+                any(),
+                eq(CHANGE_TASKBAR_PINNING or CHANGE_SHOW_LOCKED_TASKBAR),
+            )
         assertFalse(displayController.getInfo().isTransientTaskbar())
     }
 
@@ -227,7 +233,11 @@
         displayController.onConfigurationChanged(configuration)
 
         verify(displayInfoChangeListener)
-            .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
+            .onDisplayInfoChanged(
+                any(),
+                any(),
+                eq(CHANGE_TASKBAR_PINNING or CHANGE_SHOW_LOCKED_TASKBAR),
+            )
         assertFalse(displayController.getInfo().isTransientTaskbar())
     }
 
diff --git a/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java b/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java
index 1e21ee5..44df5b8 100644
--- a/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java
+++ b/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java
@@ -23,7 +23,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.util.BaseLauncherActivityTest;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.views.ActivityContext;
 
 import org.junit.Test;
@@ -64,7 +63,6 @@
     }
 
     @Test
-    @ScreenRecord  //b/378167329
     public void testAllAppsExitSearchAndFocusSearchResults() {
         loadLauncherSync();
         goToState(LauncherState.ALL_APPS);
diff --git a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
index a123170..38970fe 100644
--- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
@@ -23,7 +23,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.net.Uri;
@@ -37,6 +36,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.allapps.AllAppsRecyclerView;
 import com.android.launcher3.celllayout.FavoriteItemsTransaction;
+import com.android.launcher3.dagger.LauncherComponentProvider;
 import com.android.launcher3.icons.mono.ThemedIconDrawable;
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.util.BaseLauncherActivityTest;
@@ -139,7 +139,7 @@
         return icon;
     }
 
-    private void setThemeEnabled(boolean isEnabled) throws Exception {
+    private void setThemeEnabled(boolean isEnabled) {
         Uri uri = new Uri.Builder()
                 .scheme(ContentResolver.SCHEME_CONTENT)
                 .authority(targetContext().getPackageName() + ".grid_control")
@@ -147,11 +147,10 @@
                 .build();
         ContentValues values = new ContentValues();
         values.put("boolean_value", isEnabled);
-        try (ContentProviderClient client = targetContext().getContentResolver()
-                .acquireContentProviderClient(uri)) {
-            int result = client.update(uri, values, null);
-            assertTrue(result > 0);
-        }
+
+        int result = LauncherComponentProvider.get(targetContext()).getGridCustomizationsProxy()
+                .update(uri, values, null, null);
+        assertTrue(result > 0);
     }
 
     private void switchToAllApps() {