Merge "Revert "Play workspace reveal animation for predictive back-to-home"" into main
diff --git a/Android.bp b/Android.bp
index 73d0fce..6bd8602 100644
--- a/Android.bp
+++ b/Android.bp
@@ -475,6 +475,9 @@
     ],
     manifest: "quickstep/AndroidManifest.xml",
     min_sdk_version: "current",
+    lint: {
+        disabled_checks: ["MissingClass"],
+    },
 }
 
 // Library with all the source code and dependencies for building Launcher Go
diff --git a/OWNERS b/OWNERS
index 22efa33..db8d442 100644
--- a/OWNERS
+++ b/OWNERS
@@ -6,10 +6,8 @@
 
 adamcohen@google.com
 hyunyoungs@google.com
-twickham@google.com
 vadimt@google.com
 winsonc@google.com
-jonmiranda@google.com
 awickham@google.com
 agvard@google.com
 
@@ -29,7 +27,6 @@
 peanutbutter@google.com
 jeremysim@google.com
 atsjenk@google.com
-brianji@google.com
 hwwang@google.com
 
 # Overview eng team
@@ -46,6 +43,16 @@
 shamalip@google.com
 zakcohen@google.com
 
+# System Navigation team
+brianji@google.com
+jonmiranda@google.com
+jagrutdesai@google.com
+randypfohl@google.com
+saumyaprakash@google.com
+sukeshram@google.com
+twickham@google.com
+victortulias@google.com
+
 per-file FeatureFlags.java, globs = set noparent
 per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
 
diff --git a/aconfig/launcher_overview.aconfig b/aconfig/launcher_overview.aconfig
index 93d8d54..b299edf 100644
--- a/aconfig/launcher_overview.aconfig
+++ b/aconfig/launcher_overview.aconfig
@@ -61,4 +61,14 @@
     namespace: "launcher_overview"
     description: "Enables the non-overlapping layout for desktop windows in Overview mode."
     bug: "378011776"
+}
+
+flag {
+    name: "enable_use_top_visible_activity_for_exclude_from_recent_task"
+    namespace: "launcher_overview"
+    description: "Enables using the top visible activity for exclude from recent task instead of the activity indicies."
+    bug: "342627272"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
 }
\ No newline at end of file
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
index c6e2d8c..80d8154 100644
--- a/quickstep/AndroidManifest-launcher.xml
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -48,7 +48,7 @@
             android:stateNotNeeded="true"
             android:windowSoftInputMode="adjustPan"
             android:screenOrientation="unspecified"
-            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
             android:resizeableActivity="true"
             android:resumeWhilePausing="true"
             android:taskAffinity=""
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 8c39585..201c5f6 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -81,7 +81,7 @@
              android:stateNotNeeded="true"
              android:theme="@style/LauncherTheme"
              android:screenOrientation="behind"
-             android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
+             android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
              android:resizeableActivity="true"
              android:resumeWhilePausing="true"
              android:enableOnBackInvokedCallback="false"
diff --git a/quickstep/res/layout/overview_add_desktop_button.xml b/quickstep/res/layout/overview_add_desktop_button.xml
new file mode 100644
index 0000000..2333dd1
--- /dev/null
+++ b/quickstep/res/layout/overview_add_desktop_button.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.quickstep.views.AddDesktopButton
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apgk/res-auto"
+    android:id="@+id/add_desktop_button"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:src="@drawable/ic_desktop_add"
+    android:padding="10dp" />
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index a7f6b36..3aac1b6 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -28,7 +28,11 @@
     launcher:focusBorderColor="@color/materialColorOutline"
     launcher:hoverBorderColor="@color/materialColorPrimary">
 
-    <include layout="@layout/task_thumbnail_deprecated" />
+    <ViewStub
+        android:id="@+id/snapshot"
+        android:inflatedId="@id/snapshot"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 
     <!-- Filtering affects only alpha instead of the visibility since visibility can be altered
          separately through RecentsView#resetFromSplitSelectionState() -->
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index 4c650b9..3e6f5ed 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -33,10 +33,17 @@
     launcher:focusBorderColor="@color/materialColorOutline"
     launcher:hoverBorderColor="@color/materialColorPrimary">
 
-    <include layout="@layout/task_thumbnail_deprecated"/>
+    <ViewStub
+        android:id="@+id/snapshot"
+        android:inflatedId="@id/snapshot"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 
-    <include layout="@layout/task_thumbnail_deprecated"
-        android:id="@+id/bottomright_snapshot" />
+    <ViewStub
+        android:id="@+id/bottomright_snapshot"
+        android:inflatedId="@id/bottomright_snapshot"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 
     <!-- Filtering affects only alpha instead of the visibility since visibility can be altered
          separately through RecentsView#resetFromSplitSelectionState() -->
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index afbcdb5..3b96615 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -15,7 +15,6 @@
 -->
 <com.android.quickstep.task.thumbnail.TaskThumbnailView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/snapshot"
     android:layout_width="match_parent"
     android:layout_height="match_parent" >
@@ -53,10 +52,7 @@
         android:id="@+id/splash_icon"
         android:layout_width="@dimen/task_thumbnail_splash_icon_size"
         android:layout_height="@dimen/task_thumbnail_splash_icon_size"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_gravity="center"
         android:scaleType="fitCenter"
         android:alpha="0"
         android:importantForAccessibility="no" />
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
index 645bef6..6e36305 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
@@ -45,7 +45,8 @@
         }
         remoteWindowLimitUnminimizeTransition =
             RemoteTransition(
-                DesktopAppLaunchTransition(context, MAIN_EXECUTOR, AppLaunchType.UNMINIMIZE)
+                DesktopAppLaunchTransition(context, MAIN_EXECUTOR, AppLaunchType.UNMINIMIZE),
+                "DesktopWindowLimitUnminimize"
             )
         systemUiProxy.registerRemoteTransition(
             remoteWindowLimitUnminimizeTransition,
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index ac1ffa6..8b064d3 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -31,6 +31,8 @@
 import com.android.quickstep.SystemUiProxy
 import com.android.quickstep.TaskViewUtils
 import com.android.quickstep.views.DesktopTaskView
+import com.android.quickstep.views.TaskContainer
+import com.android.quickstep.views.TaskView
 import com.android.window.flags.Flags
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
 import java.util.function.Consumer
@@ -62,8 +64,12 @@
     }
 
     /** Launch desktop tasks from recents view */
-    fun moveToDesktop(taskId: Int, transitionSource: DesktopModeTransitionSource) {
-        systemUiProxy.moveToDesktop(taskId, transitionSource)
+    fun moveToDesktop(taskContainer: TaskContainer, transitionSource: DesktopModeTransitionSource) {
+        systemUiProxy.moveToDesktop(
+            taskContainer.task.key.id,
+            transitionSource,
+            /* transition = */ null,
+        )
     }
 
     /** Move task to external display from recents view */
@@ -72,7 +78,7 @@
     }
 
     private class RemoteDesktopLaunchTransitionRunner(
-        private val desktopTaskView: DesktopTaskView,
+        private val taskView: TaskView,
         private val animated: Boolean,
         private val stateManager: StateManager<*, *>,
         private val depthController: DepthController?,
@@ -99,7 +105,7 @@
             MAIN_EXECUTOR.execute {
                 val animator =
                     TaskViewUtils.composeRecentsDesktopLaunchAnimator(
-                        desktopTaskView,
+                        taskView,
                         stateManager,
                         depthController,
                         info,
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 1967dfd..306443e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -35,6 +35,8 @@
 import android.view.animation.Interpolator;
 import android.widget.HorizontalScrollView;
 import android.widget.TextView;
+import android.window.OnBackInvokedDispatcher;
+import android.window.WindowOnBackInvokedDispatcher;
 
 import androidx.annotation.LayoutRes;
 import androidx.annotation.NonNull;
@@ -109,6 +111,8 @@
 
     @Nullable private AnimatorSet mOpenAnimation;
 
+    private boolean mIsBackCallbackRegistered = false;
+
     @Nullable private KeyboardQuickSwitchViewController.ViewCallbacks mViewCallbacks;
 
     public KeyboardQuickSwitchView(@NonNull Context context) {
@@ -158,6 +162,34 @@
         mIsRtl = Utilities.isRtl(resources);
     }
 
+    private void registerOnBackInvokedCallback() {
+        OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
+
+        if (isOnBackInvokedCallbackEnabled(dispatcher)
+                && !mIsBackCallbackRegistered) {
+            dispatcher.registerOnBackInvokedCallback(
+                    OnBackInvokedDispatcher.PRIORITY_OVERLAY, mViewCallbacks.onBackInvokedCallback);
+            mIsBackCallbackRegistered = true;
+        }
+    }
+
+    private void unregisterOnBackInvokedCallback() {
+        OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
+
+        if (isOnBackInvokedCallbackEnabled(dispatcher)
+                && mIsBackCallbackRegistered) {
+            dispatcher.unregisterOnBackInvokedCallback(
+                    mViewCallbacks.onBackInvokedCallback);
+            mIsBackCallbackRegistered = false;
+        }
+    }
+
+    private boolean isOnBackInvokedCallbackEnabled(OnBackInvokedDispatcher dispatcher) {
+        return dispatcher instanceof WindowOnBackInvokedDispatcher
+                && ((WindowOnBackInvokedDispatcher) dispatcher).isOnBackInvokedCallbackEnabled()
+                && mViewCallbacks != null;
+    }
+
     private KeyboardQuickSwitchTaskView createAndAddTaskView(
             int index,
             boolean isFinalView,
@@ -277,6 +309,7 @@
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
+                        registerOnBackInvokedCallback();
                         animateOpen(currentFocusIndexOverride);
 
                         getViewTreeObserver().removeOnGlobalLayoutListener(this);
@@ -293,6 +326,9 @@
     }
 
     void resetViewCallbacks() {
+        // Unregister the back invoked callback after the view is closed and before the
+        // mViewCallbacks is reset.
+        unregisterOnBackInvokedCallback();
         mViewCallbacks = null;
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index e623b21..cb811d6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType.UNMINIMIZE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
@@ -25,6 +26,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.animation.AnimationUtils;
+import android.window.OnBackInvokedCallback;
 import android.window.RemoteTransition;
 
 import androidx.annotation.NonNull;
@@ -284,8 +286,8 @@
         ) {
             // This app is being unminimized - use our own transition runner.
             remoteTransition = new RemoteTransition(
-                    new DesktopAppLaunchTransition(
-                        context, MAIN_EXECUTOR, AppLaunchType.UNMINIMIZE));
+                    new DesktopAppLaunchTransition(context, MAIN_EXECUTOR, UNMINIMIZE),
+                    "DesktopKeyboardQuickSwitchUnminimize");
         }
         mControllers.taskbarActivityContext.handleGroupTaskLaunch(
                 task,
@@ -331,6 +333,7 @@
     }
 
     class ViewCallbacks {
+        public final OnBackInvokedCallback onBackInvokedCallback = () -> closeQuickSwitchView(true);
 
         boolean onKeyUp(int keyCode, KeyEvent event, boolean isRTL, boolean allowTraversal) {
             if (keyCode != KeyEvent.KEYCODE_TAB
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index f0129b4..7d75286 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -69,14 +69,17 @@
     public static final int ALL_APPS_PAGE_PROGRESS_INDEX = 1;
     public static final int WIDGETS_PAGE_PROGRESS_INDEX = 2;
     public static final int SYSUI_SURFACE_PROGRESS_INDEX = 3;
+    public static final int LAUNCHER_PAUSE_PROGRESS_INDEX = 4;
 
-    public static final int DISPLAY_PROGRESS_COUNT = 4;
+    public static final int DISPLAY_PROGRESS_COUNT = 5;
 
     private final AnimatedFloat mTaskbarInAppDisplayProgress = new AnimatedFloat(
             this::onInAppDisplayProgressChanged);
     private final MultiPropertyFactory<AnimatedFloat> mTaskbarInAppDisplayProgressMultiProp =
             new MultiPropertyFactory<>(mTaskbarInAppDisplayProgress,
                     AnimatedFloat.VALUE, DISPLAY_PROGRESS_COUNT, Float::max);
+    private final AnimatedFloat mLauncherPauseProgress = new AnimatedFloat(
+            this::onLauncherPauseProgressUpdate);
 
     private final QuickstepLauncher mLauncher;
     private final HomeVisibilityState mHomeState;
@@ -499,7 +502,8 @@
                 "MINUS_ONE_PAGE_PROGRESS_INDEX",
                 "ALL_APPS_PAGE_PROGRESS_INDEX",
                 "WIDGETS_PAGE_PROGRESS_INDEX",
-                "SYSUI_SURFACE_PROGRESS_INDEX");
+                "SYSUI_SURFACE_PROGRESS_INDEX",
+                "LAUNCHER_PAUSE_PROGRESS_INDEX");
 
         mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw);
     }
@@ -529,4 +533,39 @@
             mLauncher.getWorkspace().onOverlayScrollChanged(0);
         }
     }
+
+    /**
+     * Called when Launcher Activity resumed while staying at home.
+     * <p>
+     * Shift nav buttons up to at-home position.
+     */
+    public void onLauncherResume() {
+        mLauncherPauseProgress.animateToValue(0.0f).start();
+    }
+
+    /**
+     * Called when Launcher Activity paused while staying at home.
+     * <p>
+     * To avoid UI clash between taskbar & bottom sheet, shift nav buttons down to in-app position.
+     */
+    public void onLauncherPause() {
+        mLauncherPauseProgress.animateToValue(1.0f).start();
+    }
+
+    /**
+     * On launcher stop, avoid animating taskbar & overriding pre-existing animations.
+     */
+    public void onLauncherStop() {
+        mLauncherPauseProgress.cancelAnimation();
+        mLauncherPauseProgress.updateValue(0.0f);
+    }
+
+    private void onLauncherPauseProgressUpdate() {
+        // If we are not aligned with hotseat, setting this will clobber the 3 button nav position.
+        // So in that case, treat the progress as 0 instead.
+        float pauseProgress = isIconAlignedWithHotseat() ? mLauncherPauseProgress.value : 0;
+        onTaskbarInAppDisplayProgressUpdate(pauseProgress, LAUNCHER_PAUSE_PROGRESS_INDEX);
+    }
+
+
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt b/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
index 8d1f4f5..032eb51 100644
--- a/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
@@ -32,7 +32,8 @@
 import com.android.launcher3.views.ActivityContext
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.SystemUiProxy
-import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.DesktopTask
+import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
 import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer
 import java.util.Collections
@@ -60,20 +61,29 @@
     private val recentsModel = RecentsModel.INSTANCE[controllers.taskbarActivityContext]
 
     override fun onClick(v: View?) {
-        val filter =
-            Predicate<GroupTask> { task: GroupTask? ->
-                task != null && task.task1.key.packageName == itemInfo?.getTargetPackage()
-            }
-        recentsModel.getTasks(
-            { tasks: List<GroupTask> ->
-                // Since fetching thumbnails is asynchronous, use this set to gate until the tasks
-                // are ready to display
-                val pendingTaskIds =
-                    Collections.synchronizedSet(tasks.map { it.task1.key.id }.toMutableSet())
-                createAndShowTaskShortcutView(tasks, pendingTaskIds)
-            },
-            filter,
-        )
+        val targetPackage = itemInfo?.getTargetPackage()
+        val targetUserId = itemInfo?.user?.identifier
+        val isTargetPackageTask: (Task) -> Boolean = { task ->
+            task.key?.packageName == targetPackage && task.key.userId == targetUserId
+        }
+
+        recentsModel.getTasks { tasks ->
+            val desktopTask = tasks.filterIsInstance<DesktopTask>().firstOrNull()
+            val packageDesktopTasks =
+                (desktopTask?.tasks ?: emptyList()).filter(isTargetPackageTask)
+            val nonDesktopPackageTasks =
+                tasks.filter { isTargetPackageTask(it.task1) }.map { it.task1 }
+
+            // Add tasks from the fetched tasks, deduplicating by task ID
+            val packageTasks =
+                (packageDesktopTasks + nonDesktopPackageTasks).distinctBy { it.key.id }
+
+            // Since fetching thumbnails is asynchronous, use `awaitedTaskIds` to gate until the
+            // tasks are ready to display
+            val awaitedTaskIds = packageTasks.map { it.key.id }.toMutableSet()
+
+            createAndShowTaskShortcutView(packageTasks, awaitedTaskIds)
+        }
     }
 
     /**
@@ -83,25 +93,20 @@
      * thumbnails are processed, it creates a [TaskbarShortcutManageWindowsView] with the collected
      * thumbnails and positions it appropriately.
      */
-    private fun createAndShowTaskShortcutView(
-        tasks: List<GroupTask?>,
-        pendingTaskIds: MutableSet<Int>,
-    ) {
+    private fun createAndShowTaskShortcutView(tasks: List<Task>, pendingTaskIds: MutableSet<Int>) {
         val taskList = arrayListOf<Pair<Int, Bitmap?>>()
-        tasks.forEach { groupTask ->
-            groupTask?.task1?.let { task ->
-                recentsModel.thumbnailCache.getThumbnailInBackground(task) {
-                    thumbnailData: ThumbnailData ->
-                    pendingTaskIds.remove(task.key.id)
-                    // Add the current pair of task id and ThumbnailData to the list of all tasks
-                    if (thumbnailData.thumbnail != null) {
-                        taskList.add(task.key.id to thumbnailData.thumbnail)
-                    }
 
-                    // If the set is empty, all thumbnails have been fetched
-                    if (pendingTaskIds.isEmpty() && taskList.isNotEmpty()) {
-                        createAndPositionTaskbarShortcut(taskList)
-                    }
+        tasks.forEach { task ->
+            recentsModel.thumbnailCache.getThumbnailInBackground(task) {
+                thumbnailData: ThumbnailData ->
+                pendingTaskIds.remove(task.key.id)
+                // Add the current pair of task id and ThumbnailData to the list of all tasks
+                if (thumbnailData.thumbnail != null) {
+                    taskList.add(task.key.id to thumbnailData.thumbnail)
+                }
+                // If the set is empty, all thumbnails have been fetched
+                if (pendingTaskIds.isEmpty() && taskList.isNotEmpty()) {
+                    createAndPositionTaskbarShortcut(taskList)
                 }
             }
         }
@@ -113,13 +118,13 @@
     private fun createAndPositionTaskbarShortcut(taskList: ArrayList<Pair<Int, Bitmap?>>) {
         val onIconClickListener =
             ({ taskId: Int? ->
-                taskbarShortcutAllWindowsView.removeFromContainer()
+                taskbarShortcutAllWindowsView.animateClose()
                 if (taskId != null) {
                     SystemUiProxy.INSTANCE.get(target).showDesktopApp(taskId, null)
                 }
             })
 
-        val onOutsideClickListener = { taskbarShortcutAllWindowsView.removeFromContainer() }
+        val onOutsideClickListener = { taskbarShortcutAllWindowsView.animateClose() }
 
         taskbarShortcutAllWindowsView =
             TaskbarShortcutManageWindowsView(
@@ -172,6 +177,7 @@
         init {
             createAndShowMenuView(snapshotList, onIconClickListener, onOutsideClickListener)
             taskbarOverlayContext.dragLayer.addTouchController(this)
+            animateOpen()
         }
 
         /** Adds the carousel menu to the taskbar overlay drag layer */
@@ -245,7 +251,7 @@
                     it.action == MotionEvent.ACTION_DOWN &&
                         !taskbarOverlayContext.dragLayer.isEventOverView(menuView.rootView, it)
                 ) {
-                    removeFromContainer()
+                    animateClose()
                 }
             }
             return false
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index cb4e5e2..9ee4b95 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -866,13 +866,19 @@
             TaskbarNavButtonController navButtonController) {
         final RectF rect = new RectF();
         buttonView.setOnTouchListener((v, event) -> {
-            if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            int motionEventAction = event.getAction();
+            if (motionEventAction == MotionEvent.ACTION_DOWN) {
                 rect.set(0, 0, v.getWidth(), v.getHeight());
             }
-            boolean isCancelled = event.getAction() == MotionEvent.ACTION_CANCEL
-                    || !rect.contains(event.getX(), event.getY());
-            if (event.getAction() == MotionEvent.ACTION_MOVE && !isCancelled) return false;
-            int motionEventAction = event.getAction();
+            boolean isCancelled = motionEventAction == MotionEvent.ACTION_CANCEL
+                    || (!rect.contains(event.getX(), event.getY())
+                    && (motionEventAction == MotionEvent.ACTION_MOVE
+                    || motionEventAction == MotionEvent.ACTION_UP));
+            if (motionEventAction != MotionEvent.ACTION_DOWN
+                    && motionEventAction != MotionEvent.ACTION_UP && !isCancelled) {
+                // return early. we don't care about any other cases than DOWN, UP and CANCEL
+                return false;
+            }
             int keyEventAction = motionEventAction == MotionEvent.ACTION_DOWN
                     ? KeyEvent.ACTION_DOWN : ACTION_UP;
             navButtonController.sendBackKeyEvent(keyEventAction, isCancelled);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 0f639f9..8e2246b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -196,6 +196,12 @@
     private boolean mIsFullscreen;
     // The size we should return to when we call setTaskbarWindowFullscreen(false)
     private int mLastRequestedNonFullscreenSize;
+    /**
+     * When this is true, the taskbar window size is not updated. Requests to update the window
+     * size are stored in {@link #mLastRequestedNonFullscreenSize} and will take effect after
+     * bubbles no longer animate and {@link #setTaskbarWindowForAnimatingBubble()} is called.
+     */
+    private boolean mIsTaskbarSizeFrozenForAnimatingBubble;
 
     private NavigationMode mNavMode;
     private boolean mImeDrawsImeNavBar;
@@ -442,6 +448,8 @@
         onNavButtonsDarkIntensityChanged(sharedState.navButtonsDarkIntensity);
         onNavigationBarLumaSamplingEnabled(sharedState.mLumaSamplingDisplayId,
                 sharedState.mIsLumaSamplingEnabled);
+        setWallpaperVisible(sharedState.wallpaperVisible);
+        onTransitionModeUpdated(sharedState.barMode, true /* checkBarModes */);
 
         if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
             // W/ the flag not set this entire class gets re-created, which resets the value of
@@ -496,6 +504,13 @@
         return getBubbleControllers() != null && BubbleBarController.isBubbleBarEnabled();
     }
 
+    private boolean isBubbleBarAnimating() {
+        return mControllers
+                .bubbleControllers
+                .map(controllers -> controllers.bubbleBarViewController.isAnimatingNewBubble())
+                .orElse(false);
+    }
+
     /**
      * Returns if software keyboard is docked or input toolbar is placed at the taskbar area
      */
@@ -892,7 +907,8 @@
         ActivityOptions options = ActivityOptions.makeRemoteTransition(
                 new RemoteTransition(
                         new DesktopAppLaunchTransition(
-                                /* context= */ this, getMainExecutor(), launchType)));
+                                /* context= */ this, getMainExecutor(), launchType),
+                        "TaskbarDesktopLaunch"));
         return new ActivityOptionsWrapper(options, new RunnableList());
     }
 
@@ -1062,6 +1078,25 @@
     }
 
     /**
+     * Updates the taskbar window size according to whether bubbles are animating.
+     *
+     * <p>This method should be called when bubbles start animating and again after the animation is
+     * complete.
+     */
+    public void setTaskbarWindowForAnimatingBubble() {
+        if (isBubbleBarAnimating()) {
+            // the default window size accounts for the bubble flyout
+            setTaskbarWindowSize(getDefaultTaskbarWindowSize());
+            mIsTaskbarSizeFrozenForAnimatingBubble = true;
+        } else {
+            mIsTaskbarSizeFrozenForAnimatingBubble = false;
+            setTaskbarWindowSize(
+                    mLastRequestedNonFullscreenSize != 0
+                            ? mLastRequestedNonFullscreenSize : getDefaultTaskbarWindowSize());
+        }
+    }
+
+    /**
      * Called when drag ends or when a view is removed from the DragLayer.
      */
     void onDragEndOrViewRemoved() {
@@ -1097,11 +1132,13 @@
             size = mDeviceProfile.heightPx;
         } else {
             mLastRequestedNonFullscreenSize = size;
-            if (mIsFullscreen) {
-                // We still need to be fullscreen, so defer any change to our height until we call
-                // setTaskbarWindowFullscreen(false). For example, this could happen when dragging
-                // from the gesture region, as the drag will cancel the gesture and reset launcher's
-                // state, which in turn normally would reset the taskbar window height as well.
+            if (mIsFullscreen || mIsTaskbarSizeFrozenForAnimatingBubble) {
+                // We either still need to be fullscreen or a bubble is still animating, so defer
+                // any change to our height until setTaskbarWindowFullscreen(false) is called or
+                // setTaskbarWindowForAnimatingBubble() is called after the bubble animation
+                // completed. For example, this could happen when dragging from the gesture region,
+                // as the drag will cancel the gesture and reset launcher's state, which in turn
+                // normally would reset the taskbar window height as well.
                 return;
             }
         }
@@ -1285,9 +1322,25 @@
         } else if (tag instanceof TaskItemInfo info) {
             RemoteTransition remoteTransition = canUnminimizeDesktopTask(info.getTaskId())
                     ? createUnminimizeRemoteTransition() : null;
-            UI_HELPER_EXECUTOR.execute(() ->
-                    SystemUiProxy.INSTANCE.get(this).showDesktopApp(
-                            info.getTaskId(), remoteTransition));
+
+            if (areDesktopTasksVisible() && recents != null) {
+                TaskView taskView = recents.getTaskViewByTaskId(info.getTaskId());
+                if (taskView == null) return;
+                RunnableList runnableList = taskView.launchWithAnimation();
+                if (runnableList != null) {
+                    runnableList.add(() ->
+                            // wrapped it in runnable here since we need the post for DW to be
+                            // ready. if we don't other DW will be gone and only the launched task
+                            // will show.
+                            UI_HELPER_EXECUTOR.execute(() ->
+                                    SystemUiProxy.INSTANCE.get(this).showDesktopApp(
+                                            info.getTaskId(), remoteTransition)));
+                }
+            } else {
+                UI_HELPER_EXECUTOR.execute(() ->
+                        SystemUiProxy.INSTANCE.get(this).showDesktopApp(
+                                info.getTaskId(), remoteTransition));
+            }
             mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(
                     /* stash= */ true);
         } else if (tag instanceof WorkspaceItemInfo) {
@@ -1445,8 +1498,8 @@
 
     private RemoteTransition createUnminimizeRemoteTransition() {
         return new RemoteTransition(
-                new DesktopAppLaunchTransition(
-                        this, getMainExecutor(), AppLaunchType.UNMINIMIZE));
+                new DesktopAppLaunchTransition(this, getMainExecutor(), AppLaunchType.UNMINIMIZE),
+                "TaskbarDesktopUnminimize");
     }
 
     /**
@@ -1531,7 +1584,17 @@
                                                 .launchAppPair((AppPairIcon) launchingIconView,
                                                         -1 /*cuj*/)));
                     } else {
-                        startItemInfoActivity(itemInfos.get(0), foundTask);
+                        if (areDesktopTasksVisible()) {
+                            RunnableList runnableList = recents.launchDesktopTaskView();
+                            // Wrapping it in runnable so we post after DW is ready for the app
+                            // launch.
+                            if (runnableList != null) {
+                                runnableList.add(() -> UI_HELPER_EXECUTOR.execute(
+                                        () -> startItemInfoActivity(itemInfos.get(0), foundTask)));
+                            }
+                        } else {
+                            startItemInfoActivity(itemInfos.get(0), foundTask);
+                        }
                     }
                 }
         );
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
index bdc7f92..444c356 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
@@ -49,6 +49,8 @@
     public static final int FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS = 1 << 6;
     // User has multi instance window open.
     public static final int FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN = 1 << 7;
+    // User has taskbar overflow open.
+    public static final int FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW = 1 << 8;
 
     @IntDef(flag = true, value = {
             FLAG_AUTOHIDE_SUSPEND_FULLSCREEN,
@@ -59,6 +61,7 @@
             FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR,
             FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
             FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN,
+            FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AutohideSuspendFlag {}
@@ -138,6 +141,8 @@
                 "FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR");
         appendFlag(str, flags, FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN,
                 "FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN");
+        appendFlag(str, flags, FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW,
+                "FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW");
         return str.toString();
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 2db7961..250e33a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -1018,7 +1018,12 @@
 
         @Override
         public void onRecentsAnimationFinished(RecentsAnimationController controller) {
-            endGestureStateOverride(!controller.getFinishTargetIsLauncher(), false /*canceled*/);
+            endGestureStateOverride(!controller.getFinishTargetIsLauncher(),
+                    controller.getLauncherIsVisibleAtFinish(), false /*canceled*/);
+        }
+
+        private void endGestureStateOverride(boolean finishedToApp, boolean canceled) {
+            endGestureStateOverride(finishedToApp, finishedToApp, canceled);
         }
 
         /**
@@ -1028,11 +1033,13 @@
          *
          * @param finishedToApp {@code true} if the recents animation finished to showing an app and
          *                      not workspace or overview
+         * @param launcherIsVisible {code true} if launcher is visible at finish
          * @param canceled      {@code true} if the recents animation was canceled instead of
          *                      finishing
          *                      to completion
          */
-        private void endGestureStateOverride(boolean finishedToApp, boolean canceled) {
+        private void endGestureStateOverride(boolean finishedToApp, boolean launcherIsVisible,
+                boolean canceled) {
             mCallbacks.removeListener(this);
             mTaskBarRecentsAnimationListener = null;
             ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null);
@@ -1041,18 +1048,27 @@
                 mSkipNextRecentsAnimEnd = false;
                 return;
             }
-            updateStateForUserFinishedToApp(finishedToApp);
+            updateStateForUserFinishedToApp(finishedToApp, launcherIsVisible);
         }
     }
 
     /**
+     * @see #updateStateForUserFinishedToApp(boolean, boolean)
+     */
+    private void updateStateForUserFinishedToApp(boolean finishedToApp) {
+        updateStateForUserFinishedToApp(finishedToApp, !finishedToApp);
+    }
+
+    /**
      * Updates the visible state immediately to ensure a seamless handoff.
      *
      * @param finishedToApp True iff user is in an app.
+     * @param launcherIsVisible True iff launcher is still visible (ie. transparent app)
      */
-    private void updateStateForUserFinishedToApp(boolean finishedToApp) {
+    private void updateStateForUserFinishedToApp(boolean finishedToApp,
+            boolean launcherIsVisible) {
         // Update the visible state immediately to ensure a seamless handoff
-        boolean launcherVisible = !finishedToApp;
+        boolean launcherVisible = !finishedToApp || launcherIsVisible;
         updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, false);
         updateStateForFlag(FLAG_VISIBLE, launcherVisible);
         applyState();
@@ -1061,7 +1077,7 @@
         if (DEBUG) {
             Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp);
         }
-        controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
+        controller.updateStateForFlag(FLAG_IN_APP, finishedToApp && !launcherIsVisible);
         controller.applyState();
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index ff8e4a8..9407e73 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -113,7 +113,7 @@
     private static final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor(
             Settings.Secure.NAV_BAR_KIDS_MODE);
 
-    private final Context mContext;
+    private final Context mWindowContext;
     private final @Nullable Context mNavigationBarPanelContext;
     private WindowManager mWindowManager;
     private boolean mAddedWindow;
@@ -231,7 +231,7 @@
             @NonNull DesktopVisibilityController desktopVisibilityController) {
         Display display =
                 context.getSystemService(DisplayManager.class).getDisplay(context.getDisplayId());
-        mContext = context.createWindowContext(display,
+        mWindowContext = context.createWindowContext(display,
                 ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL,
                 null);
         mAllAppsActionManager = allAppsActionManager;
@@ -240,30 +240,47 @@
                 : null;
         mDesktopVisibilityController = desktopVisibilityController;
         if (enableTaskbarNoRecreate()) {
-            mWindowManager = mContext.getSystemService(WindowManager.class);
-            FrameLayout taskbarRootLayout = new FrameLayout(mContext) {
-                @Override
-                public boolean dispatchTouchEvent(MotionEvent ev) {
-                    // The motion events can be outside the view bounds of task bar, and hence
-                    // manually dispatching them to the drag layer here.
-                    TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
-                    if (taskbar != null && taskbar.getDragLayer().isAttachedToWindow()) {
-                        return taskbar.getDragLayer().dispatchTouchEvent(ev);
-                    }
-                    return super.dispatchTouchEvent(ev);
-                }
-            };
-            addTaskbarRootLayoutToMap(getDefaultDisplayId(), taskbarRootLayout);
+            mWindowManager = mWindowContext.getSystemService(WindowManager.class);
+            createTaskbarRootLayout(getDefaultDisplayId());
         }
-        mDefaultNavButtonController = new TaskbarNavButtonController(
+        mDefaultNavButtonController = createDefaultNavButtonController(context, navCallbacks);
+        mDefaultComponentCallbacks = createDefaultComponentCallbacks();
+        SettingsCache.INSTANCE.get(mWindowContext)
+                .register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
+        SettingsCache.INSTANCE.get(mWindowContext)
+                .register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+        Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
+        mWindowContext.registerComponentCallbacks(mDefaultComponentCallbacks);
+        mShutdownReceiver.register(mWindowContext, Intent.ACTION_SHUTDOWN);
+        UI_HELPER_EXECUTOR.execute(() -> {
+            mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
+                    mWindowContext,
+                    SYSTEM_ACTION_ID_TASKBAR,
+                    new Intent(ACTION_SHOW_TASKBAR).setPackage(mWindowContext.getPackageName()),
+                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+            mTaskbarBroadcastReceiver.register(
+                    mWindowContext, RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
+        });
+
+        debugWhyTaskbarNotDestroyed("TaskbarManager created");
+        recreateTaskbar();
+    }
+
+    @NonNull
+    private TaskbarNavButtonController createDefaultNavButtonController(Context context,
+            TaskbarNavButtonCallbacks navCallbacks) {
+        return new TaskbarNavButtonController(
                 context,
                 navCallbacks,
-                SystemUiProxy.INSTANCE.get(mContext),
-                ContextualEduStatsManager.INSTANCE.get(mContext),
+                SystemUiProxy.INSTANCE.get(mWindowContext),
+                ContextualEduStatsManager.INSTANCE.get(mWindowContext),
                 new Handler(),
-                new ContextualSearchInvoker(mContext));
-        mDefaultComponentCallbacks = new ComponentCallbacks() {
-            private Configuration mOldConfig = mContext.getResources().getConfiguration();
+                new ContextualSearchInvoker(mWindowContext));
+    }
+
+    private ComponentCallbacks createDefaultComponentCallbacks() {
+        return new ComponentCallbacks() {
+            private Configuration mOldConfig = mWindowContext.getResources().getConfiguration();
 
             @Override
             public void onConfigurationChanged(Configuration newConfig) {
@@ -273,7 +290,7 @@
                         "TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
                 // TODO: adapt this logic to be specific to different displays.
                 DeviceProfile dp = mUserUnlocked
-                        ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext)
+                        ? LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext)
                         : null;
                 int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
 
@@ -317,25 +334,6 @@
             @Override
             public void onLowMemory() { }
         };
-        SettingsCache.INSTANCE.get(mContext)
-                .register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
-        SettingsCache.INSTANCE.get(mContext)
-                .register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
-        Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
-        mContext.registerComponentCallbacks(mDefaultComponentCallbacks);
-        mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
-        UI_HELPER_EXECUTOR.execute(() -> {
-            mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
-                    mContext,
-                    SYSTEM_ACTION_ID_TASKBAR,
-                    new Intent(ACTION_SHOW_TASKBAR).setPackage(mContext.getPackageName()),
-                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-            mTaskbarBroadcastReceiver.register(
-                    mContext, RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
-        });
-
-        debugWhyTaskbarNotDestroyed("TaskbarManager created");
-        recreateTaskbar();
     }
 
     private void destroyAllTaskbars() {
@@ -360,7 +358,7 @@
             removeTaskbarFromMap(displayId);
         }
         DeviceProfile dp = mUserUnlocked ?
-                LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
+                LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
         if (dp == null || !isTaskbarEnabled(dp)) {
             removeTaskbarRootViewFromWindow(displayId);
         }
@@ -407,7 +405,7 @@
      */
     public void onUserUnlocked() {
         mUserUnlocked = true;
-        DisplayController.INSTANCE.get(mContext).addChangeListener(mRecreationListener);
+        DisplayController.INSTANCE.get(mWindowContext).addChangeListener(mRecreationListener);
         recreateTaskbar();
         addTaskbarRootViewToWindow(getDefaultDisplayId());
     }
@@ -470,7 +468,7 @@
                 return ql.getUnfoldTransitionProgressProvider();
             }
         } else {
-            return SystemUiProxy.INSTANCE.get(mContext).getUnfoldTransitionProvider();
+            return SystemUiProxy.INSTANCE.get(mWindowContext).getUnfoldTransitionProvider();
         }
         return null;
     }
@@ -514,7 +512,7 @@
         Trace.beginSection("recreateTaskbar");
         try {
             DeviceProfile dp = mUserUnlocked ?
-                LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
+                    LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
 
             // All Apps action is unrelated to navbar unification, so we only need to check DP.
             final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
@@ -528,7 +526,7 @@
                 + " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
                 + " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
             if (!isTaskbarEnabled || !isLargeScreenTaskbar) {
-                SystemUiProxy.INSTANCE.get(mContext)
+                SystemUiProxy.INSTANCE.get(mWindowContext)
                     .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
                 if (!isTaskbarEnabled) {
                     return;
@@ -537,9 +535,7 @@
 
             TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
             if (enableTaskbarNoRecreate() || taskbar == null) {
-                taskbar = new TaskbarActivityContext(mContext,
-                        mNavigationBarPanelContext, dp, mDefaultNavButtonController,
-                        mUnfoldProgressProvider, mDesktopVisibilityController);
+                taskbar = createTaskbarActivityContext(dp, displayId);
             } else {
                 taskbar.updateDeviceProfile(dp);
             }
@@ -560,7 +556,6 @@
                 taskbarRootLayout.addView(taskbar.getDragLayer());
                 taskbar.notifyUpdateLayoutParams();
             }
-            addTaskbarToMap(displayId, taskbar);
         } finally {
             Trace.endSection();
         }
@@ -716,22 +711,23 @@
         mRecentsViewContainer = null;
         debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
         removeActivityCallbacksAndListeners();
-        mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext);
+        mTaskbarBroadcastReceiver.unregisterReceiverSafely(mWindowContext);
         destroyAllTaskbars();
         if (mUserUnlocked) {
-            DisplayController.INSTANCE.get(mContext).removeChangeListener(mRecreationListener);
+            DisplayController.INSTANCE.get(mWindowContext).removeChangeListener(
+                    mRecreationListener);
         }
-        SettingsCache.INSTANCE.get(mContext)
+        SettingsCache.INSTANCE.get(mWindowContext)
                 .unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
-        SettingsCache.INSTANCE.get(mContext)
+        SettingsCache.INSTANCE.get(mWindowContext)
                 .unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
         Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
-        mContext.unregisterComponentCallbacks(mDefaultComponentCallbacks);
-        mShutdownReceiver.unregisterReceiverSafely(mContext);
+        mWindowContext.unregisterComponentCallbacks(mDefaultComponentCallbacks);
+        mShutdownReceiver.unregisterReceiverSafely(mWindowContext);
     }
 
     public @Nullable TaskbarActivityContext getCurrentActivityContext() {
-        return getTaskbarForDisplay(mContext.getDisplayId());
+        return getTaskbarForDisplay(mWindowContext.getDisplayId());
     }
 
     public void dumpLogs(String prefix, PrintWriter pw) {
@@ -773,6 +769,19 @@
         return mTaskbars.get(displayId);
     }
 
+
+    /**
+     * Creates a {@link TaskbarActivityContext} for the given display and adds it to the map.
+     */
+    private TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp, int displayId) {
+        TaskbarActivityContext newTaskbar = new TaskbarActivityContext(mWindowContext,
+                mNavigationBarPanelContext, dp, mDefaultNavButtonController,
+                mUnfoldProgressProvider, mDesktopVisibilityController);
+
+        addTaskbarToMap(displayId, newTaskbar);
+        return newTaskbar;
+    }
+
     /**
      * Adds the {@link TaskbarActivityContext} associated with the given display ID to taskbar
      * map if there is not already a taskbar mapped to that displayId.
@@ -796,6 +805,26 @@
     }
 
     /**
+     * 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) {
+        FrameLayout newTaskbarRootLayout = new FrameLayout(mWindowContext) {
+            @Override
+            public boolean dispatchTouchEvent(MotionEvent ev) {
+                // The motion events can be outside the view bounds of task bar, and hence
+                // manually dispatching them to the drag layer here.
+                TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+                if (taskbar != null && taskbar.getDragLayer().isAttachedToWindow()) {
+                    return taskbar.getDragLayer().dispatchTouchEvent(ev);
+                }
+                return super.dispatchTouchEvent(ev);
+            }
+        };
+        addTaskbarRootLayoutToMap(displayId, newTaskbarRootLayout);
+    }
+
+    /**
      * Retrieves the root layout of the taskbar for the specified display.
      *
      * @param displayId The ID of the display for which to retrieve the taskbar root layout.
@@ -812,7 +841,7 @@
      * @param rootLayout The taskbar root layout {@link FrameLayout} to add to the map.
      */
     private void addTaskbarRootLayoutToMap(int displayId, FrameLayout rootLayout) {
-        if (!mRootLayouts.contains(displayId)) {
+        if (!mRootLayouts.contains(displayId) && rootLayout != null) {
             mRootLayouts.put(displayId, rootLayout);
         }
     }
@@ -829,7 +858,7 @@
     }
 
     private int getDefaultDisplayId() {
-        return mContext.getDisplayId();
+        return mWindowContext.getDisplayId();
     }
 
     /** Temp logs for b/254119092. */
@@ -839,15 +868,15 @@
 
         boolean activityTaskbarPresent = mActivity != null
                 && mActivity.getDeviceProfile().isTaskbarPresent;
-        boolean contextTaskbarPresent = mUserUnlocked
-                && LauncherAppState.getIDP(mContext).getDeviceProfile(mContext).isTaskbarPresent;
+        boolean contextTaskbarPresent = mUserUnlocked && LauncherAppState.getIDP(mWindowContext)
+                .getDeviceProfile(mWindowContext).isTaskbarPresent;
         if (activityTaskbarPresent == contextTaskbarPresent) {
-            log.add("mActivity and mContext agree taskbarIsPresent=" + contextTaskbarPresent);
+            log.add("mActivity and mWindowContext agree taskbarIsPresent=" + contextTaskbarPresent);
             Log.d(TASKBAR_NOT_DESTROYED_TAG, log.toString());
             return;
         }
 
-        log.add("mActivity and mContext device profiles have different values, add more logs.");
+        log.add("mActivity & mWindowContext device profiles have different values, add more logs.");
 
         log.add("\tmActivity logs:");
         log.add("\t\tmActivity=" + mActivity);
@@ -857,13 +886,13 @@
             log.add("\t\tmActivity.getDeviceProfile().isTaskbarPresent="
                     + activityTaskbarPresent);
         }
-        log.add("\tmContext logs:");
-        log.add("\t\tmContext=" + mContext);
-        log.add("\t\tmContext.getResources().getConfiguration()="
-                + mContext.getResources().getConfiguration());
+        log.add("\tmWindowContext logs:");
+        log.add("\t\tmWindowContext=" + mWindowContext);
+        log.add("\t\tmWindowContext.getResources().getConfiguration()="
+                + mWindowContext.getResources().getConfiguration());
         if (mUserUnlocked) {
-            log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(mContext).isTaskbarPresent="
-                    + contextTaskbarPresent);
+            log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(mWindowContext)"
+                    + ".isTaskbarPresent=" + contextTaskbarPresent);
         } else {
             log.add("\t\tCouldn't get DeviceProfile because !mUserUnlocked");
         }
@@ -876,6 +905,6 @@
 
     @VisibleForTesting
     public Context getWindowContext() {
-        return mContext;
+        return mWindowContext;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
index 8775766..0ed6669 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
@@ -35,6 +35,7 @@
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 import androidx.core.graphics.ColorUtils;
 
 import com.android.app.animation.Interpolators;
@@ -315,6 +316,11 @@
         invalidate();
     }
 
+    @VisibleForTesting
+    public List<Integer> getItemIds() {
+        return mItems.stream().map(task -> task.key.id).toList();
+    }
+
     /**
      * Called when a task is updated. If the task is contained within the view, it's cached value
      * gets updated. If the task is shown within the icon, invalidates the view, so the task icon
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 2e0bae5..abf35a2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
 import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
 
@@ -309,9 +310,7 @@
      */
     SystemShortcut.Factory<BaseTaskbarContext> createNewWindowShortcutFactory() {
         return (context, itemInfo, originalView) -> {
-            ComponentKey key = itemInfo.getComponentKey();
-            AppInfo app = getApp(key);
-            if (app != null && app.supportsMultiInstance()) {
+            if (shouldShowMultiInstanceOptions(itemInfo)) {
                 return new NewWindowTaskbarShortcut<>(context, itemInfo, originalView);
             }
             return null;
@@ -325,9 +324,7 @@
      */
     public SystemShortcut.Factory<BaseTaskbarContext> createManageWindowsShortcutFactory() {
         return (context, itemInfo, originalView) -> {
-            ComponentKey key = itemInfo.getComponentKey();
-            AppInfo app = getApp(key);
-            if (app != null && app.supportsMultiInstance()) {
+            if (shouldShowMultiInstanceOptions(itemInfo)) {
                 return new ManageWindowsTaskbarShortcut<>(context, itemInfo, originalView,
                         mControllers);
             }
@@ -336,6 +333,16 @@
     }
 
     /**
+     * Determines whether to show multi-instance options for a given item.
+     */
+    private boolean shouldShowMultiInstanceOptions(ItemInfo itemInfo) {
+        ComponentKey key = itemInfo.getComponentKey();
+        AppInfo app = getApp(key);
+        return app != null && app.supportsMultiInstance()
+                && itemInfo.container != CONTAINER_ALL_APPS;
+    }
+
+    /**
      * A single menu item ("Split left," "Split right," or "Split top") that executes a split
      * from the taskbar, as if the user performed a drag and drop split.
      * Includes an onClick method that initiates the actual split.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index 3d57de4..a059b22 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -326,8 +326,8 @@
     }
 
     /**
-     * Returns the hotseat items updated so that any item that points to a package with a running
-     * task also references that task.
+     * Returns the hotseat items updated so that any item that points to a package+user with a
+     * running task also references that task.
      */
     private fun updateHotseatItemsFromRunningTasks(
         groupTasks: List<GroupTask>,
@@ -338,8 +338,10 @@
                 itemInfo
             } else {
                 val foundTask =
-                    groupTasks.find { task -> task.task1.key.packageName == itemInfo.targetPackage }
-                        ?: return@map itemInfo
+                    groupTasks.find { task ->
+                        task.task1.key.packageName == itemInfo.targetPackage &&
+                            task.task1.key.userId == itemInfo.user.identifier
+                    } ?: return@map itemInfo
                 TaskItemInfo(foundTask.task1.key.id, itemInfo as WorkspaceItemInfo)
             }
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 130b9b7..de9eee4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -34,6 +34,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.view.DisplayCutout;
 import android.view.InputDevice;
@@ -74,8 +75,10 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Predicate;
 
 /**
@@ -134,6 +137,8 @@
 
     private final int mNumStaticViews;
 
+    private Set<GroupTask> mPrevRecentTasks = Collections.emptySet();
+
     public TaskbarView(@NonNull Context context) {
         this(context, null);
     }
@@ -458,7 +463,7 @@
 
         // Skip static views and potential All Apps divider, if they are on the left.
         mNextViewIndex = mIsRtl ? 0 : mNumStaticViews;
-        if (getChildAt(mNextViewIndex) == mTaskbarDividerContainer) {
+        if (getChildAt(mNextViewIndex) == mTaskbarDividerContainer && !mAddedDividerForRecents) {
             mNextViewIndex++;
         }
 
@@ -613,7 +618,7 @@
         // accounted for when comparing current icon count to max number of icons.
         int nonTaskIconsToBeAdded = 1;
 
-        boolean supportsOverflow = Flags.taskbarOverflow();
+        boolean supportsOverflow = Flags.taskbarOverflow() && recentTasks.size() > 1;
         int overflowSize = 0;
         if (supportsOverflow) {
             mIdealNumIcons = mNextViewIndex + recentTasks.size() + nonTaskIconsToBeAdded;
@@ -636,6 +641,7 @@
         }
 
         // Add Recent/Running icons.
+        final Set<GroupTask> recentTasksSet = new ArraySet<>(recentTasks);
         for (GroupTask task : recentTasks) {
             if (mTaskbarOverflowView != null && overflownTasks != null
                     && overflownTasks.size() < itemsToAddToOverflow) {
@@ -664,12 +670,18 @@
             }
 
             View recentIcon = null;
-            while (isNextViewInSection(GroupTask.class)) {
+            // If a task is new, we should not reuse a view so that it animates in when it is added.
+            final boolean canReuseView = !taskbarRecentsLayoutTransition()
+                    || mPrevRecentTasks.contains(task);
+            while (canReuseView && isNextViewInSection(GroupTask.class)) {
                 recentIcon = getChildAt(mNextViewIndex);
 
                 // see if the view can be reused
                 if ((recentIcon.getSourceLayoutResId() != expectedLayoutResId)
-                        || (isCollection && (recentIcon.getTag() != task))) {
+                        || (isCollection && (recentIcon.getTag() != task))
+                        // Remove view corresponding to removed task so that it animates out.
+                        || (taskbarRecentsLayoutTransition()
+                                && !recentTasksSet.contains(recentIcon.getTag()))) {
                     removeAndRecycle(recentIcon);
                     recentIcon = null;
                 } else {
@@ -699,6 +711,8 @@
         while (isNextViewInSection(GroupTask.class)) {
             removeAndRecycle(getChildAt(mNextViewIndex));
         }
+
+        mPrevRecentTasks = recentTasksSet;
     }
 
     private boolean isNextViewInSection(Class<?> tagClass) {
@@ -839,6 +853,8 @@
             iconEnd += mAllAppsButtonTranslationOffset;
         }
 
+        mControllerCallbacks.onPreLayoutChildren();
+
         int count = getChildCount();
         for (int i = count; i > 0; i--) {
             View child = getChildAt(i - 1);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index 4d77ab2..c7841c1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -16,8 +16,11 @@
 
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.Flags.taskbarRecentsLayoutTransition;
+import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
+import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -109,6 +112,13 @@
         return new TaskbarHoverToolTipController(mActivity, mTaskbarView, icon);
     }
 
+    /** Callback invoked before Taskbar icons are laid out. */
+    void onPreLayoutChildren() {
+        if (enableTaskbarPinning() && taskbarRecentsLayoutTransition()) {
+            mControllers.taskbarViewController.updateTaskbarIconTranslationXForPinning();
+        }
+    }
+
     /**
      * Notifies launcher to update icon alignment.
      */
@@ -180,6 +190,9 @@
         if (mTaskbarView.getTaskbarOverflowView() != null) {
             mTaskbarView.getTaskbarOverflowView().setIsActive(
                     !mTaskbarView.getTaskbarOverflowView().getIsActive());
+            mControllers.taskbarAutohideSuspendController
+                    .updateFlag(FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW,
+                            mTaskbarView.getTaskbarOverflowView().getIsActive());
         }
         mControllers.keyboardQuickSwitchController.toggleQuickSwitchViewForTaskbar(
                 mControllers.taskbarViewController.getTaskIdsForPinnedApps(),
@@ -190,6 +203,8 @@
         if (mTaskbarView.getTaskbarOverflowView() != null) {
             mTaskbarView.getTaskbarOverflowView().setIsActive(false);
         }
+        mControllers.taskbarAutohideSuspendController.updateFlag(
+                FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW, false);
     }
 
     private float getDividerCenterX() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 4acf2fe..89f4f59 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -15,10 +15,17 @@
  */
 package com.android.launcher3.taskbar;
 
+import static android.animation.LayoutTransition.APPEARING;
+import static android.animation.LayoutTransition.CHANGE_APPEARING;
+import static android.animation.LayoutTransition.CHANGE_DISAPPEARING;
+import static android.animation.LayoutTransition.DISAPPEARING;
+
+import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.app.animation.Interpolators.FINAL_FRAME;
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.Flags.taskbarOverflow;
+import static com.android.launcher3.Flags.taskbarRecentsLayoutTransition;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
@@ -40,13 +47,17 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
+import android.animation.LayoutTransition.TransitionListener;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.graphics.Rect;
+import android.util.FloatProperty;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.Nullable;
@@ -74,6 +85,7 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.MultiPropertyFactory;
+import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.quickstep.util.GroupTask;
@@ -114,6 +126,11 @@
     /** Used if an unexpected edge case is hit in {@link #getPositionInHotseat}. */
     private static final float ERROR_POSITION_IN_HOTSEAT_NOT_FOUND = -100;
 
+    private static final int TRANSITION_DELAY = 50;
+    private static final int TRANSITION_DEFAULT_DURATION = 500;
+    private static final int TRANSITION_FADE_IN_DURATION = 167;
+    private static final int TRANSITION_FADE_OUT_DURATION = 83;
+
     private static boolean sEnableModelLoadingForTests = true;
 
     private final TaskbarActivityContext mActivity;
@@ -158,7 +175,9 @@
 
     private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener =
             (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                updateTaskbarIconTranslationXForPinning();
+                if (!taskbarRecentsLayoutTransition()) {
+                    updateTaskbarIconTranslationXForPinning();
+                }
                 if (BubbleBarController.isBubbleBarEnabled()) {
                     mControllers.navbarButtonsViewController.onLayoutsUpdated();
                 }
@@ -432,7 +451,7 @@
         }
     }
 
-    private void updateTaskbarIconTranslationXForPinning() {
+    void updateTaskbarIconTranslationXForPinning() {
         View[] iconViews = mTaskbarView.getIconViews();
         float scale = mTaskbarIconTranslationXForPinning.value;
         float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension(
@@ -1076,6 +1095,89 @@
     /** Called when there's a change in running apps to update the UI. */
     public void commitRunningAppsToUI() {
         mModelCallbacks.commitRunningAppsToUI();
+        if (taskbarRecentsLayoutTransition() && mTaskbarView.getLayoutTransition() == null) {
+            // Set up after the first commit so that the initial recents do not animate (janky).
+            mTaskbarView.setLayoutTransition(createLayoutTransitionForRunningApps());
+        }
+    }
+
+    private LayoutTransition createLayoutTransitionForRunningApps() {
+        LayoutTransition layoutTransition = new LayoutTransition();
+        layoutTransition.setDuration(TRANSITION_DEFAULT_DURATION);
+        layoutTransition.addTransitionListener(new TransitionListener() {
+
+            @Override
+            public void startTransition(
+                    LayoutTransition transition, ViewGroup container, View view, int type) {
+                if (type == APPEARING) {
+                    view.setAlpha(0f);
+                    view.setScaleX(0f);
+                    view.setScaleY(0f);
+                }
+            }
+
+            @Override
+            public void endTransition(
+                    LayoutTransition transition, ViewGroup container, View view, int type) {
+                // Do nothing.
+            }
+        });
+
+        // Appearing.
+        AnimatorSet appearingSet = new AnimatorSet();
+        Animator appearingAlphaAnimator = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
+        appearingAlphaAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0f,
+                (float) TRANSITION_FADE_IN_DURATION / TRANSITION_DEFAULT_DURATION));
+        Animator appearingScaleAnimator = ObjectAnimator.ofFloat(null, SCALE_PROPERTY, 0f, 1f);
+        appearingScaleAnimator.setInterpolator(EMPHASIZED);
+        appearingSet.playTogether(appearingAlphaAnimator, appearingScaleAnimator);
+        layoutTransition.setAnimator(APPEARING, appearingSet);
+        layoutTransition.setStartDelay(APPEARING, TRANSITION_DELAY);
+
+        // Disappearing.
+        AnimatorSet disappearingSet = new AnimatorSet();
+        Animator disappearingAlphaAnimator = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
+        disappearingAlphaAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR,
+                (float) TRANSITION_DELAY / TRANSITION_DEFAULT_DURATION,
+                (float) (TRANSITION_DELAY + TRANSITION_FADE_OUT_DURATION)
+                        / TRANSITION_DEFAULT_DURATION));
+        Animator disappearingScaleAnimator = ObjectAnimator.ofFloat(null, SCALE_PROPERTY, 1f, 0f);
+        disappearingScaleAnimator.setInterpolator(EMPHASIZED);
+        disappearingSet.playTogether(disappearingAlphaAnimator, disappearingScaleAnimator);
+        layoutTransition.setAnimator(DISAPPEARING, disappearingSet);
+
+        // Change transitions.
+        FloatProperty<View> translateXPinning = new FloatProperty<>("translateXPinning") {
+            @Override
+            public void setValue(View view, float value) {
+                getTranslationXForPinning(view).setValue(value);
+            }
+
+            @Override
+            public Float get(View view) {
+                return getTranslationXForPinning(view).getValue();
+            }
+
+            private MultiProperty getTranslationXForPinning(View view) {
+                return ((Reorderable) view).getTranslateDelegate()
+                        .getTranslationX(INDEX_TASKBAR_PINNING_ANIM);
+            }
+        };
+        AnimatorSet changeSet = new AnimatorSet();
+        changeSet.playTogether(
+                layoutTransition.getAnimator(CHANGE_APPEARING),
+                ObjectAnimator.ofFloat(null, translateXPinning, 0f, 1f));
+
+        // Change appearing.
+        layoutTransition.setAnimator(CHANGE_APPEARING, changeSet);
+        layoutTransition.setInterpolator(CHANGE_APPEARING, EMPHASIZED);
+
+        // Change disappearing.
+        layoutTransition.setAnimator(CHANGE_DISAPPEARING, changeSet);
+        layoutTransition.setInterpolator(CHANGE_DISAPPEARING, EMPHASIZED);
+        layoutTransition.setStartDelay(CHANGE_DISAPPEARING, TRANSITION_DELAY);
+
+        return layoutTransition;
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 987937e..5b3c233 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -405,8 +405,12 @@
             mBubbleBarViewController.showOverflow(true);
         }
 
-        // Adds and removals have happened, update visibility before any other visual changes.
-        mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
+        // Update the visibility if this is the initial state or if there are no bubbles.
+        // If this is the initial bubble, the bubble bar will become visible as part of the
+        // animation.
+        if (update.initialState || mBubbles.isEmpty()) {
+            mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
+        }
         mBubbleStashedHandleViewController.ifPresent(
                 controller -> controller.setHiddenForBubbles(mBubbles.isEmpty()));
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 219a2b3..37c6194 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -81,6 +81,7 @@
 
     public static final long FADE_OUT_ANIM_POSITION_DURATION_MS = 100L;
     public static final long FADE_IN_ANIM_ALPHA_DURATION_MS = 100L;
+    public static final long FADE_OUT_BUBBLE_BAR_DURATION_MS = 150L;
     private static final String TAG = "BubbleBarView";
     // TODO: (b/273594744) calculate the amount of space we have and base the max on that
     //  if it's smaller than 5.
@@ -146,6 +147,9 @@
     // collapsed state and 1 to the fully expanded state.
     private ValueAnimator mWidthAnimator = createExpansionAnimator(/* expanding = */ false);
 
+    @Nullable
+    private ValueAnimator mDismissAnimator = null;
+
     /** An animator used for animating individual bubbles in the bubble bar while expanded. */
     @Nullable
     private BubbleAnimator mBubbleAnimator = null;
@@ -798,11 +802,11 @@
     /** Removes the given bubble from the bubble bar. */
     public void removeBubble(View bubble) {
         if (isExpanded()) {
-            // TODO b/347062801 - animate the bubble bar if the last bubble is removed
             final boolean dismissedByDrag = mDraggedBubbleView == bubble;
             if (dismissedByDrag) {
                 mDismissedByDragBubbleView = mDraggedBubbleView;
             }
+            boolean removingLastRemainingBubble = getBubbleChildCount() == 1;
             int bubbleCount = getChildCount();
             mBubbleAnimator = new BubbleAnimator(mIconSize, mExpandedBarIconsSpacing,
                     bubbleCount, mBubbleBarLocation.isOnLeft(isLayoutRtl()));
@@ -840,7 +844,10 @@
                             : bubbleIndex == bubbleCount - 1;
             mBubbleAnimator.animateRemovedBubble(
                     indexOfChild(bubble), indexOfChild(mSelectedBubbleView), removingLastBubble,
-                    listener);
+                    removingLastRemainingBubble, listener);
+            if (removingLastRemainingBubble && mDismissAnimator == null) {
+                createDismissAnimator().start();
+            }
         } else {
             removeView(bubble);
         }
@@ -861,6 +868,28 @@
         updateDotsAndBadgesIfCollapsed();
     }
 
+    private ValueAnimator createDismissAnimator() {
+        ValueAnimator animator =
+                ValueAnimator.ofFloat(0, 1).setDuration(FADE_OUT_BUBBLE_BAR_DURATION_MS);
+        animator.setInterpolator(Interpolators.EMPHASIZED);
+        Runnable onEnd = () -> {
+            mDismissAnimator = null;
+            setAlpha(0);
+        };
+        addAnimationCallBacks(animator, /* onStart= */ null, onEnd,
+                /* onUpdate= */ anim -> setAlpha(1 - anim.getAnimatedFraction()));
+        mDismissAnimator = animator;
+        return animator;
+    }
+
+    /** Dismisses the bubble bar */
+    public void dismiss(Runnable onDismissed) {
+        if (mDismissAnimator == null) {
+            createDismissAnimator().start();
+        }
+        addAnimationCallBacks(mDismissAnimator, null, onDismissed, null);
+    }
+
     /**
      * Return child views in the order which they are shown on the screen.
      * <p>
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 1e0a778..dd1b0ca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -170,7 +170,8 @@
                 mBubbleBarContainer, createFlyoutPositioner(), createFlyoutCallbacks());
         mBubbleBarViewAnimator = new BubbleBarViewAnimator(
                 mBarView, mBubbleStashController, mBubbleBarFlyoutController,
-                createBubbleBarParentViewController(), mBubbleBarController::showExpandedView);
+                createBubbleBarParentViewController(), mBubbleBarController::showExpandedView,
+                () -> setHiddenForBubbles(false));
         mTaskbarViewPropertiesProvider = taskbarViewPropertiesProvider;
         onBubbleBarConfigurationChanged(/* animate= */ false);
         mActivity.addOnDeviceProfileChangeListener(
@@ -328,7 +329,7 @@
         return new BubbleBarParentViewHeightUpdateNotifier() {
             @Override
             public void updateTopBoundary() {
-                mActivity.setTaskbarWindowSize(mActivity.getDefaultTaskbarWindowSize());
+                mActivity.setTaskbarWindowForAnimatingBubble();
             }
         };
     }
@@ -580,15 +581,23 @@
 
     /** Returns maximum height of the bubble bar with the flyout view. */
     public int getBubbleBarWithFlyoutMaximumHeight() {
-        if (!isBubbleBarVisible()) return 0;
+        if (!isBubbleBarVisible() && !isAnimatingNewBubble()) return 0;
         int bubbleBarTopOnHome = (int) (mBubbleStashController.getBubbleBarVerticalCenterForHome()
-                + mBarView.getBubbleBarCollapsedHeight() / 2);
-        int result = (int) (bubbleBarTopOnHome + mBarView.getArrowHeight());
+                + mBarView.getBubbleBarCollapsedHeight() / 2 + mBarView.getArrowHeight());
         if (isAnimatingNewBubble()) {
-            // when animating new bubble add the maximum height of the flyout view
-            result += mBubbleBarFlyoutController.getMaximumFlyoutHeight();
+            if (mTaskbarStashController.isInApp() && mBubbleStashController.getHasHandleView()) {
+                // when animating a bubble in an app, the bubble bar will be higher than its
+                // position on home
+                float bubbleBarTopDistanceFromBottom =
+                        -mBubbleStashController.getBubbleBarTranslationYForTaskbar()
+                                + mBarView.getHeight();
+                return (int) bubbleBarTopDistanceFromBottom
+                        + mBubbleBarFlyoutController.getMaximumFlyoutHeight();
+            }
+            return bubbleBarTopOnHome + mBubbleBarFlyoutController.getMaximumFlyoutHeight();
+        } else {
+            return bubbleBarTopOnHome;
         }
-        return result;
     }
 
     /**
@@ -597,13 +606,17 @@
     public void setHiddenForBubbles(boolean hidden) {
         if (mHiddenForNoBubbles != hidden) {
             mHiddenForNoBubbles = hidden;
-            updateVisibilityForStateChange();
             if (hidden) {
-                mBarView.setAlpha(0);
-                mBarView.setExpanded(false);
-                adjustTaskbarAndHotseatToBubbleBarState(/* isBubbleBarExpanded = */ false);
+                mBarView.dismiss(() -> {
+                    updateVisibilityForStateChange();
+                    mBarView.setExpanded(false);
+                    adjustTaskbarAndHotseatToBubbleBarState(/* isBubbleBarExpanded= */ false);
+                    mActivity.bubbleBarVisibilityChanged(/* isVisible= */ false);
+                });
+            } else {
+                updateVisibilityForStateChange();
+                mActivity.bubbleBarVisibilityChanged(/* isVisible= */ true);
             }
-            mActivity.bubbleBarVisibilityChanged(!hidden);
         }
     }
 
@@ -636,7 +649,6 @@
         }
     }
 
-    // TODO: (b/273592694) animate it
     private void updateVisibilityForStateChange() {
         if (!mHiddenForSysui && !mHiddenForNoBubbles && !mHiddenForStashed) {
             mBarView.setVisibility(VISIBLE);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt
index 8af8ffb..3604167 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt
@@ -49,23 +49,30 @@
         bubbleIndex: Int,
         selectedBubbleIndex: Int,
         removingLastBubble: Boolean,
-        listener: Listener
+        removingLastRemainingBubble: Boolean,
+        listener: Listener,
     ) {
         animator = createAnimator(listener)
-        state = State.RemovingBubble(bubbleIndex, selectedBubbleIndex, removingLastBubble)
+        state =
+            State.RemovingBubble(
+                bubbleIndex = bubbleIndex,
+                selectedBubbleIndex = selectedBubbleIndex,
+                removingLastBubble = removingLastBubble,
+                removingLastRemainingBubble = removingLastRemainingBubble,
+            )
         animator.start()
     }
 
     fun animateNewAndRemoveOld(
         selectedBubbleIndex: Int,
         removedBubbleIndex: Int,
-        listener: Listener
+        listener: Listener,
     ) {
         animator = createAnimator(listener)
         state =
             State.AddingAndRemoving(
                 selectedBubbleIndex = selectedBubbleIndex,
-                removedBubbleIndex = removedBubbleIndex
+                removedBubbleIndex = removedBubbleIndex,
             )
         animator.start()
     }
@@ -111,20 +118,22 @@
                 getBubbleTranslationXWhileScalingBubble(
                     bubbleIndex = bubbleIndex,
                     scalingBubbleIndex = 0,
-                    bubbleScale = animator.animatedFraction
+                    bubbleScale = animator.animatedFraction,
                 )
+
             is State.RemovingBubble ->
                 getBubbleTranslationXWhileScalingBubble(
                     bubbleIndex = bubbleIndex,
                     scalingBubbleIndex = state.bubbleIndex,
-                    bubbleScale = 1 - animator.animatedFraction
+                    bubbleScale = 1 - animator.animatedFraction,
                 )
+
             is State.AddingAndRemoving ->
                 getBubbleTranslationXWhileAddingBubbleAtLimit(
                     bubbleIndex = bubbleIndex,
                     removedBubbleIndex = state.removedBubbleIndex,
                     addedBubbleScale = animator.animatedFraction,
-                    removedBubbleScale = 1 - animator.animatedFraction
+                    removedBubbleScale = 1 - animator.animatedFraction,
                 )
         }
     }
@@ -176,10 +185,11 @@
                     getBubbleTranslationXWhileScalingBubble(
                         bubbleIndex = state.selectedBubbleIndex,
                         scalingBubbleIndex = 0,
-                        bubbleScale = animator.animatedFraction
+                        bubbleScale = animator.animatedFraction,
                     )
                 tx + iconSize / 2f
             }
+
             is State.RemovingBubble -> getArrowPositionWhenRemovingBubble(state)
             is State.AddingAndRemoving -> {
                 // we never remove the selected bubble, so the arrow stays pointing to its center
@@ -188,22 +198,23 @@
                         bubbleIndex = state.selectedBubbleIndex,
                         removedBubbleIndex = state.removedBubbleIndex,
                         addedBubbleScale = animator.animatedFraction,
-                        removedBubbleScale = 1 - animator.animatedFraction
+                        removedBubbleScale = 1 - animator.animatedFraction,
                     )
                 tx + iconSize / 2f
             }
         }
     }
 
-    private fun getArrowPositionWhenRemovingBubble(state: State.RemovingBubble): Float {
-        return if (state.selectedBubbleIndex != state.bubbleIndex) {
-            // if we're not removing the selected bubble, the selected bubble doesn't change so just
-            // return the translation X of the selected bubble and add half icon
+    private fun getArrowPositionWhenRemovingBubble(state: State.RemovingBubble): Float =
+        if (state.selectedBubbleIndex != state.bubbleIndex || state.removingLastRemainingBubble) {
+            // if we're not removing the selected bubble or if we're removing the last remaining
+            // bubble, the selected bubble doesn't change so just return the translation X of the
+            // selected bubble and add half icon
             val tx =
                 getBubbleTranslationXWhileScalingBubble(
                     bubbleIndex = state.selectedBubbleIndex,
                     scalingBubbleIndex = state.bubbleIndex,
-                    bubbleScale = 1 - animator.animatedFraction
+                    bubbleScale = 1 - animator.animatedFraction,
                 )
             tx + iconSize / 2f
         } else {
@@ -238,7 +249,6 @@
                 }
             }
         }
-    }
 
     /**
      * Returns the translation X for the bubble at index {@code bubbleIndex} when the bubble bar is
@@ -251,7 +261,7 @@
     private fun getBubbleTranslationXWhileScalingBubble(
         bubbleIndex: Int,
         scalingBubbleIndex: Int,
-        bubbleScale: Float
+        bubbleScale: Float,
     ): Float {
         val iconAndSpacing = iconSize + expandedBarIconSpacing
         // the bubble is scaling from the center, so we need to adjust its translation so
@@ -300,7 +310,7 @@
         bubbleIndex: Int,
         removedBubbleIndex: Int,
         addedBubbleScale: Float,
-        removedBubbleScale: Float
+        removedBubbleScale: Float,
     ): Float {
         val iconAndSpacing = iconSize + expandedBarIconSpacing
         // the bubbles are scaling from the center, so we need to adjust their translation so
@@ -377,7 +387,9 @@
             /** The index of the selected bubble. */
             val selectedBubbleIndex: Int,
             /** Whether the bubble being removed is also the last bubble. */
-            val removingLastBubble: Boolean
+            val removingLastBubble: Boolean,
+            /** Whether we're removing the last remaining bubble. */
+            val removingLastRemainingBubble: Boolean,
         ) : State
 
         /** A new bubble is being added and an old bubble is being removed from the bubble bar. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
index f5a6655..3bff58b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -42,6 +42,7 @@
     private val bubbleBarFlyoutController: BubbleBarFlyoutController,
     private val bubbleBarParentViewHeightUpdateNotifier: BubbleBarParentViewHeightUpdateNotifier,
     private val onExpanded: Runnable,
+    private val onBubbleBarVisible: Runnable,
     private val scheduler: Scheduler = HandlerScheduler(bubbleBarView),
 ) {
 
@@ -397,9 +398,12 @@
         // prepare the bubble bar for the animation
         bubbleBarView.translationY = bubbleBarView.height.toFloat()
         bubbleBarView.visibility = VISIBLE
+        onBubbleBarVisible.run()
         bubbleBarView.alpha = 1f
         bubbleBarView.scaleX = 1f
         bubbleBarView.scaleY = 1f
+        bubbleBarView.setBackgroundScaleX(1f)
+        bubbleBarView.setBackgroundScaleY(1f)
 
         val translationTracker = TranslationTracker(bubbleBarView.translationY)
 
@@ -511,23 +515,21 @@
 
     /** Interrupts the animation due to touching the bubble bar or flyout. */
     fun interruptForTouch() {
+        animatingBubble?.hideAnimation?.let { scheduler.cancel(it) }
         PhysicsAnimator.getInstance(bubbleBarView).cancelIfRunning()
         bubbleStashController.getStashedHandlePhysicsAnimator().cancelIfRunning()
         cancelFlyout()
-        val hideAnimation = animatingBubble?.hideAnimation ?: return
-        scheduler.cancel(hideAnimation)
-        bubbleBarView.relativePivotY = 1f
+        resetBubbleBarPropertiesOnInterrupt()
         clearAnimatingBubble()
     }
 
     /** Notifies the animator that the taskbar area was touched during an animation. */
     fun onStashStateChangingWhileAnimating() {
+        animatingBubble?.hideAnimation?.let { scheduler.cancel(it) }
         cancelFlyout()
-        val hideAnimation = animatingBubble?.hideAnimation ?: return
-        scheduler.cancel(hideAnimation)
         clearAnimatingBubble()
         bubbleStashController.getStashedHandlePhysicsAnimator().cancelIfRunning()
-        bubbleBarView.relativePivotY = 1f
+        resetBubbleBarPropertiesOnInterrupt()
         bubbleStashController.onNewBubbleAnimationInterrupted(
             /* isStashed= */ bubbleBarView.alpha == 0f,
             bubbleBarView.translationY,
@@ -541,7 +543,7 @@
         scheduler.cancel(hideAnimation)
         animatingBubble = null
         bubbleStashController.getStashedHandlePhysicsAnimator().cancelIfRunning()
-        bubbleBarView.relativePivotY = 1f
+        resetBubbleBarPropertiesOnInterrupt()
         // stash the bubble bar since the IME is now visible
         bubbleStashController.onNewBubbleAnimationInterrupted(
             /* isStashed= */ true,
@@ -679,6 +681,12 @@
         bubbleStashController.showBubbleBarImmediate()
     }
 
+    private fun resetBubbleBarPropertiesOnInterrupt() {
+        bubbleBarView.relativePivotY = 1f
+        bubbleBarView.scaleX = 1f
+        bubbleBarView.scaleY = 1f
+    }
+
     private fun <T> PhysicsAnimator<T>?.cancelIfRunning() {
         if (this?.isRunning() == true) cancel()
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt
index 63db012..7c718e5b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt
@@ -63,8 +63,6 @@
             return rect
         }
 
-    fun getFlyoutMaxHeight(): Int = BubbleBarFlyoutView.getMaximumViewHeight(container.context)
-
     fun setUpAndShowFlyout(message: BubbleBarFlyoutMessage, onInit: () -> Unit, onEnd: () -> Unit) {
         flyout?.let(container::removeView)
         val flyout = BubbleBarFlyoutView(container.context, positioner, flyoutScheduler)
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
index df00696..3e3f569 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -224,12 +224,13 @@
 
     override fun isBubbleBarVisible(): Boolean = bubbleBarViewController.hasBubbles() && !isStashed
 
-    override fun onNewBubbleAnimationInterrupted(isStashed: Boolean, bubbleBarTranslationY: Float) =
+    override fun onNewBubbleAnimationInterrupted(isStashed: Boolean, bubbleBarTranslationY: Float) {
         if (isStashed) {
             stashBubbleBarImmediate()
         } else {
             showBubbleBarImmediate(bubbleBarTranslationY)
         }
+    }
 
     /** Check if [ev] belongs to the stash handle or the bubble bar views. */
     override fun isEventOverBubbleBarViews(ev: MotionEvent): Boolean {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index cc51adc..eeac169 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -601,7 +601,7 @@
             case QUICK_SWITCH_STATE_ORDINAL: {
                 RecentsView rv = getOverviewPanel();
                 TaskView currentPageTask = rv.getCurrentPageTaskView();
-                TaskView fallbackTask = rv.getTaskViewAt(0);
+                TaskView fallbackTask = rv.getFirstTaskView();
                 if (currentPageTask != null || fallbackTask != null) {
                     TaskView taskToLaunch = currentPageTask;
                     if (currentPageTask == null) {
@@ -801,6 +801,10 @@
         if (mLauncherUnfoldAnimationController != null) {
             mLauncherUnfoldAnimationController.onResume();
         }
+
+        if (mTaskbarUIController != null && FeatureFlags.enableHomeTransitionListener()) {
+            mTaskbarUIController.onLauncherResume();
+        }
     }
 
     @Override
@@ -821,6 +825,18 @@
                         .playPlaceholderDismissAnim(this, LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
             }
         }
+
+        if (mTaskbarUIController != null && FeatureFlags.enableHomeTransitionListener()) {
+            mTaskbarUIController.onLauncherPause();
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        if (mTaskbarUIController != null && FeatureFlags.enableHomeTransitionListener()) {
+            mTaskbarUIController.onLauncherStop();
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
index 374db6a..2e2d7cc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -63,7 +63,7 @@
 
     override fun createFadeOutAnimOptions(): ActivityOptions =
         ActivityOptions.makeBasic().apply {
-            remoteTransition = RemoteTransition(FadeOutRemoteTransition())
+            remoteTransition = RemoteTransition(FadeOutRemoteTransition(), "FadeOut")
         }
 
     override fun queryAllUsers(): Map<UserHandle, UserIconInfo> {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 9164405..9dec332 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -256,7 +256,7 @@
         mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
         mLauncher.getActionsView().getVisibilityAlpha().updateValue(
                 (fromState.getVisibleElements(mLauncher) & OVERVIEW_ACTIONS) != 0 ? 1f : 0f);
-        mRecentsView.setTaskIconScaledDown(true);
+        mRecentsView.setTaskIconVisible(false);
 
         float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
         // As we drag right, animate the following properties:
@@ -340,7 +340,7 @@
                 public void onAnimationEnd(Animator animation) {
                     onAnimationToStateCompleted(OVERVIEW);
                     // Animate the icon after onAnimationToStateCompleted() so it doesn't clobber.
-                    mRecentsView.animateUpTaskIconScale();
+                    mRecentsView.startIconFadeInOnGestureComplete();
                 }
             });
             overviewAnim.start();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index 4df0f63..8c440b1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -50,7 +50,7 @@
     boolean canInterceptTouch(MotionEvent ev) {
         if (mRecentsView.getTaskViewCount() > 0) {
             // Allow swiping up in the gap between the hotseat and overview.
-            return ev.getY() >= mRecentsView.getTaskViewAt(0).getBottom();
+            return ev.getY() >= mRecentsView.getFirstTaskView().getBottom();
         } else {
             // If there are no tasks, we only intercept if we're below the hotseat height.
             return isTouchOverHotseat(mLauncher, ev);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 31e4e33..d673720 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -149,7 +149,7 @@
         mOverviewPanel.setFullscreenProgress(progress);
         if (progress > UPDATE_SYSUI_FLAGS_THRESHOLD) {
             int sysuiFlags = 0;
-            TaskView tv = mOverviewPanel.getTaskViewAt(0);
+            TaskView tv = mOverviewPanel.getFirstTaskView();
             if (tv != null) {
                 sysuiFlags = tv.getTaskContainers().getFirst().getSysUiStatusNavFlags();
             }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 202276e..d622987 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -66,7 +66,7 @@
 
     protected final CONTAINER mContainer;
     private final SingleAxisSwipeDetector mDetector;
-    private final RecentsView mRecentsView;
+    private final RecentsView<?, ?> mRecentsView;
     private final Rect mTempRect = new Rect();
     private final boolean mIsRtl;
 
@@ -157,17 +157,15 @@
             } else {
                 mTaskBeingDragged = null;
 
-                for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
-                    TaskView view = mRecentsView.getTaskViewAt(i);
-
-                    if (mRecentsView.isTaskViewVisible(view) && mContainer.getDragLayer()
-                            .isEventOverView(view, ev)) {
+                for (TaskView taskView : mRecentsView.getTaskViews()) {
+                    if (mRecentsView.isTaskViewVisible(taskView) && mContainer.getDragLayer()
+                            .isEventOverView(taskView, ev)) {
                         // Disable swiping up and down if the task overlay is modal.
                         if (isRecentsModal()) {
                             mTaskBeingDragged = null;
                             break;
                         }
-                        mTaskBeingDragged = view;
+                        mTaskBeingDragged = taskView;
                         int upDirection = mRecentsView.getPagedOrientationHandler()
                                 .getUpDirection(mIsRtl);
 
@@ -179,10 +177,10 @@
                         // - We support gestures to enter overview
                         // - It's the focused task if in grid view
                         // - The task is snapped
-                        mAllowGoingDown = i == mRecentsView.getCurrentPage()
+                        mAllowGoingDown = taskView == mRecentsView.getCurrentPageTaskView()
                                 && DisplayController.getNavigationMode(mContainer).hasGestures
                                 && (!mRecentsView.showAsGrid() || mTaskBeingDragged.isLargeTile())
-                                && mRecentsView.isTaskInExpectedScrollPosition(i);
+                                && mRecentsView.isTaskInExpectedScrollPosition(taskView);
 
                         directionsToDetectScroll = mAllowGoingDown ? DIRECTION_BOTH : upDirection;
                         break;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index a006198..3740a68 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -33,11 +33,14 @@
 import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
 import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
+import static com.android.launcher3.Flags.msdlFeedback;
 import static com.android.launcher3.PagedView.INVALID_PAGE;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -80,6 +83,8 @@
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.Pair;
+import android.util.TimeUtils;
+import android.view.Choreographer;
 import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
@@ -119,13 +124,14 @@
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
+import com.android.quickstep.fallback.window.RecentsWindowFactory;
 import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
@@ -147,6 +153,7 @@
 import com.android.quickstep.views.RecentsViewContainer;
 import com.android.quickstep.views.TaskContainer;
 import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.TaskViewType;
 import com.android.systemui.animation.TransitionAnimator;
 import com.android.systemui.contextualeducation.GestureType;
 import com.android.systemui.shared.recents.model.Task;
@@ -162,6 +169,8 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.shared.startingsurface.SplashScreenExitAnimationUtils;
 
+import com.google.android.msdl.data.model.MSDLToken;
+
 import kotlin.Unit;
 
 import java.util.ArrayList;
@@ -204,6 +213,8 @@
     protected MultiStateCallback mStateCallback;
     protected boolean mCanceled;
     private boolean mRecentsViewScrollLinked = false;
+    // The previous task view type before the user quick switches between tasks
+    private TaskViewType mPreviousTaskViewType;
 
     private final Runnable mLauncherOnDestroyCallback = () -> {
         ActiveGestureProtoLogProxy.logLauncherDestroyed();
@@ -299,7 +310,7 @@
     private static final int LOG_NO_OP_PAGE_INDEX = -1;
 
     protected final TaskAnimationManager mTaskAnimationManager;
-    protected final RecentsWindowManager mRecentsWindowManager;
+    protected final RecentsWindowFactory mRecentsWindowFactory;
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
     private RunningWindowAnim[] mRunningWindowAnim;
     // Possible second animation running at the same time as mRunningWindowAnim
@@ -360,10 +371,13 @@
     @Nullable
     private RemoteAnimationTargets.ReleaseCheck mSwipePipToHomeReleaseCheck = null;
 
+    private final MSDLPlayerWrapper mMSDLPlayerWrapper;
+
     public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
             long touchTimeMs, boolean continuingLastGesture,
-            InputConsumerController inputConsumer, RecentsWindowManager recentsWindowManager) {
+            InputConsumerController inputConsumer, RecentsWindowFactory recentsWindowFactory,
+            MSDLPlayerWrapper msdlPlayerWrapper) {
         super(context, deviceState, gestureState);
         mContainerInterface = gestureState.getContainerInterface();
         mContextInitListener =
@@ -379,7 +393,7 @@
                     endLauncherTransitionController();
                 }, new InputProxyHandlerFactory(mContainerInterface, mGestureState));
         mTaskAnimationManager = taskAnimationManager;
-        mRecentsWindowManager = recentsWindowManager;
+        mRecentsWindowFactory = recentsWindowFactory;
         mTouchTimeMs = touchTimeMs;
         mContinuingLastGesture = continuingLastGesture;
 
@@ -390,6 +404,8 @@
         mSplashMainWindowShiftLength = -res
                 .getDimensionPixelSize(R.dimen.starting_surface_exit_animation_window_shift_length);
 
+        mMSDLPlayerWrapper = msdlPlayerWrapper;
+
         initTransitionEndpoints(mRemoteTargetHandles[0].getTaskViewSimulator()
                 .getOrientationState().getLauncherDeviceProfile());
         initStateCallbacks();
@@ -693,6 +709,10 @@
             return;
         }
         mRecentsView.onGestureAnimationStart(runningTasks, mDeviceState.getRotationTouchHelper());
+        TaskView currentPageTaskView = mRecentsView.getCurrentPageTaskView();
+        if (currentPageTaskView != null) {
+            mPreviousTaskViewType = currentPageTaskView.getType();
+        }
     }
 
     private void launcherFrameDrawn() {
@@ -1476,21 +1496,29 @@
             return;
         }
 
-        StatsLogManager.EventEnum event;
+        ArrayList<StatsLogManager.EventEnum> events = new ArrayList<>();
         switch (endTarget) {
             case HOME:
-                event = LAUNCHER_HOME_GESTURE;
+                events.add(LAUNCHER_HOME_GESTURE);
                 break;
             case RECENTS:
-                event = LAUNCHER_OVERVIEW_GESTURE;
+                events.add(LAUNCHER_OVERVIEW_GESTURE);
                 break;
             case LAST_TASK:
             case NEW_TASK:
-                event = mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
-                        : LAUNCHER_QUICKSWITCH_RIGHT;
+                events.add(mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
+                        : LAUNCHER_QUICKSWITCH_RIGHT);
+                if (targetTask != null && DesktopModeStatus.canEnterDesktopMode(mContext)
+                        && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH.isTrue()) {
+                    if (targetTask.getType() == TaskViewType.DESKTOP) {
+                        events.add(LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE);
+                    } else if (mPreviousTaskViewType == TaskViewType.DESKTOP) {
+                        events.add(LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE);
+                    }
+                }
                 break;
             default:
-                event = IGNORE;
+                events.add(IGNORE);
         }
         StatsLogger logger = StatsLogManager.newInstance(
                         mContainer != null ? mContainer.asContext() : mContext).logger()
@@ -1507,7 +1535,7 @@
                 ? LOG_NO_OP_PAGE_INDEX
                 : mRecentsView.getNextPage();
         logger.withRank(pageIndex);
-        logger.log(event);
+        events.forEach(logger::log);
     }
 
     protected abstract HomeAnimationFactory createHomeAnimationFactory(
@@ -1734,13 +1762,30 @@
     }
 
     private void handOffAnimation(PointF velocityPxPerMs) {
-        if (!TransitionAnimator.Companion.longLivedReturnAnimationsEnabled()
-                || mRecentsAnimationController == null) {
+        if (!TransitionAnimator.Companion.longLivedReturnAnimationsEnabled()) {
+            return;
+        }
+
+        // This function is not guaranteed to be called inside a frame. We try to access the frame
+        // time immediately, but if we're not inside a frame we must post a callback to be run at
+        // the beginning of the next frame.
+        try  {
+            handOffAnimationInternal(Choreographer.getInstance().getFrameTime(), velocityPxPerMs);
+        } catch (IllegalStateException e) {
+            Choreographer.getInstance().postFrameCallback(
+                    frameTimeNanos -> handOffAnimationInternal(
+                            frameTimeNanos / TimeUtils.NANOS_PER_MS, velocityPxPerMs));
+        }
+    }
+
+    private void handOffAnimationInternal(long timestamp, PointF velocityPxPerMs) {
+        if (mRecentsAnimationController == null) {
             return;
         }
 
         Pair<RemoteAnimationTarget[], WindowAnimationState[]> targetsAndStates =
-                extractTargetsAndStates(mRemoteTargetHandles, velocityPxPerMs);
+                extractTargetsAndStates(
+                        mRemoteTargetHandles, timestamp, velocityPxPerMs);
         mRecentsAnimationController.handOffAnimation(
                 targetsAndStates.first, targetsAndStates.second);
         ActiveGestureProtoLogProxy.logHandOffAnimation();
@@ -1976,6 +2021,7 @@
     @UiThread
     private void startNewTask() {
         TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
+        doLogGesture(NEW_TASK, taskToLaunch);
         startNewTask(success -> {
             if (!success) {
                 reset();
@@ -1984,7 +2030,6 @@
                 endLauncherTransitionController();
                 updateSysUiFlags(1 /* windowProgress == overview */);
             }
-            doLogGesture(NEW_TASK, taskToLaunch);
         });
     }
 
@@ -2085,6 +2130,7 @@
         if (mRecentsView != null) {
             mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
         }
+        mGestureState.getContainerInterface().setOnDeferredActivityLaunchCallback(null);
     }
 
     private void resetStateForAnimationCancel() {
@@ -2252,7 +2298,11 @@
     }
 
     protected void performHapticFeedback() {
-        VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+        if (msdlFeedback()) {
+            mMSDLPlayerWrapper.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR);
+        } else {
+            VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+        }
     }
 
     public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 9b56fd4..b0c69cf 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -62,6 +62,7 @@
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.util.RectFSpringAnim;
@@ -102,9 +103,10 @@
 
     public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
-            boolean continuingLastGesture, InputConsumerController inputConsumer) {
+            boolean continuingLastGesture, InputConsumerController inputConsumer,
+            MSDLPlayerWrapper msdlPlayerWrapper) {
         super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
-                continuingLastGesture, inputConsumer, null);
+                continuingLastGesture, inputConsumer, null, msdlPlayerWrapper);
 
         mRunningOverHome = mGestureState.getRunningTask() != null
                 && mGestureState.getRunningTask().isHomeTask();
diff --git a/quickstep/src/com/android/quickstep/FallbackWindowInterface.java b/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
index 832c093..a202ebd 100644
--- a/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
@@ -41,6 +41,8 @@
 import com.android.quickstep.util.ContextInitListener;
 import com.android.quickstep.views.RecentsView;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -51,21 +53,23 @@
  */
 public final class FallbackWindowInterface extends BaseWindowInterface{
 
-    private static FallbackWindowInterface INSTANCE;
+    static Map<Integer, FallbackWindowInterface> sWindowInterfaceMap = new HashMap<>();
 
     private final RecentsWindowManager mRecentsWindowManager;
-
     /**
      * This is only null before init() or after destroy()
      */
     @Nullable
-    public static FallbackWindowInterface getInstance(){
-        return INSTANCE;
+    public static FallbackWindowInterface getInstance(int displayId) {
+        return sWindowInterfaceMap.get(displayId);
     }
 
-    public static void init(RecentsWindowManager recentsWindowManager) {
-        if (INSTANCE == null) {
-            INSTANCE = new FallbackWindowInterface(recentsWindowManager);
+    /**
+     * initializing instance and mapping it to display id
+     */
+    public static void init(int displayId, RecentsWindowManager recentsWindowManager) {
+        if (!sWindowInterfaceMap.containsKey(displayId)) {
+            sWindowInterfaceMap.put(displayId, new FallbackWindowInterface(recentsWindowManager));
         }
     }
 
@@ -75,7 +79,7 @@
     }
 
     public void destroy() {
-        INSTANCE = null;
+        sWindowInterfaceMap.clear();
     }
 
     /** 2 */
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 6087dc2..0ddd87b 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.StableViewInfo;
 import com.android.launcher3.views.ClipIconView;
 import com.android.launcher3.views.FloatingIconView;
@@ -67,9 +68,10 @@
 
     public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
-            boolean continuingLastGesture, InputConsumerController inputConsumer) {
+            boolean continuingLastGesture, InputConsumerController inputConsumer,
+            MSDLPlayerWrapper msdlPlayerWrapper) {
         super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
-                continuingLastGesture, inputConsumer, null);
+                continuingLastGesture, inputConsumer, null, msdlPlayerWrapper);
     }
 
 
diff --git a/quickstep/src/com/android/quickstep/OWNERS b/quickstep/src/com/android/quickstep/OWNERS
new file mode 100644
index 0000000..868e0ab
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OWNERS
@@ -0,0 +1,9 @@
+# System Navigation team
+brianji@google.com
+jonmiranda@google.com
+jagrutdesai@google.com
+randypfohl@google.com
+saumyaprakash@google.com
+sukeshram@google.com
+twickham@google.com
+victortulias@google.com
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index 461f963..c09bf3e 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -70,7 +70,7 @@
     private val taskAnimationManager: TaskAnimationManager,
     private val dispatcherProvider: DispatcherProvider = ProductionDispatchers,
 ) {
-    private val coroutineScope = CoroutineScope(SupervisorJob() + dispatcherProvider.default)
+    private val coroutineScope = CoroutineScope(SupervisorJob() + dispatcherProvider.background)
 
     private val commandQueue = ConcurrentLinkedDeque<CommandInfo>()
 
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 1f6c671..bac5e64 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -180,7 +180,8 @@
             // The default home app is a different launcher. Use the fallback Overview instead.
 
             if (Flags.enableLauncherOverviewInWindow() || Flags.enableFallbackOverviewInWindow()) {
-                mContainerInterface = FallbackWindowInterface.getInstance();
+                mContainerInterface =
+                        FallbackWindowInterface.getInstance(mDeviceState.getDisplayId());
             } else {
                 mContainerInterface = FallbackActivityInterface.INSTANCE;
             }
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 2828a84..a5d2f3e 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -208,8 +208,10 @@
         OverviewComponentObserver observer = new OverviewComponentObserver(mContext, rads);
         try {
             RecentsViewContainer container = observer.getContainerInterface().getCreatedContainer();
+            WindowInsets insets = container == null
+                    ? null : container.getRootView().getRootWindowInsets();
 
-            return container == null ? null : container.getRootView().getRootWindowInsets();
+            return insets == null ? super.getWindowInsets() : insets;
         } finally {
             observer.onDestroy();
             rads.destroy();
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 6075294..61a150b 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -19,6 +19,8 @@
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME;
 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
@@ -362,6 +364,14 @@
     }
 
     @Override
+    public void onUiChangedWhileSleeping() {
+        super.onUiChangedWhileSleeping();
+        // Dismiss recents and navigate to home if the device goes to sleep
+        // while in recents.
+        startHome();
+    }
+
+    @Override
     protected void onResume() {
         super.onResume();
         AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), OVERVIEW_STATE_ORDINAL);
@@ -382,6 +392,32 @@
 
         // Set screen title for Talkback
         setTitle(R.string.accessibility_recent_apps);
+
+        restoreState(savedInstanceState);
+    }
+
+    @Override
+    protected void onSaveInstanceState(@NonNull Bundle outState) {
+        outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal);
+        super.onSaveInstanceState(outState);
+    }
+
+    /**
+     * Restores the previous state, if it exists.
+     *
+     * @param savedState The previous state.
+     */
+    private void restoreState(Bundle savedState) {
+        if (savedState == null) {
+            return;
+        }
+
+        if (savedState.getBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME)) {
+            // RecentsState is only restored after theme changes.
+            int stateOrdinal = savedState.getInt(RUNTIME_STATE, RecentsState.DEFAULT.ordinal);
+            RecentsState recentsState = RecentsState.stateFromOrdinal(stateOrdinal);
+            mStateManager.goToState(recentsState, /*animated=*/false);
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 055aadb..145773d 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -56,6 +56,8 @@
     private boolean mFinishRequested = false;
     // Only valid when mFinishRequested == true.
     private boolean mFinishTargetIsLauncher;
+    // Only valid when mFinishRequested == true
+    private boolean mLauncherIsVisibleAtFinish;
     private RunnableList mPendingFinishCallbacks = new RunnableList();
 
     public RecentsAnimationController(RecentsAnimationControllerCompat controller,
@@ -130,13 +132,27 @@
     }
 
     @UiThread
+    public void finish(boolean toRecents, boolean launcherIsVisibleAtFinish,
+            Runnable onFinishComplete, boolean sendUserLeaveHint) {
+        Preconditions.assertUIThread();
+        finishController(toRecents, launcherIsVisibleAtFinish, onFinishComplete, sendUserLeaveHint,
+                false);
+    }
+
+    @UiThread
     public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
-        finishController(toRecents, callback, sendUserLeaveHint, false /* forceFinish */);
+        finishController(toRecents, false, callback, sendUserLeaveHint, false /* forceFinish */);
     }
 
     @UiThread
     public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint,
             boolean forceFinish) {
+        finishController(toRecents, toRecents, callback, sendUserLeaveHint, forceFinish);
+    }
+
+    @UiThread
+    public void finishController(boolean toRecents, boolean launcherIsVisibleAtFinish,
+            Runnable callback, boolean sendUserLeaveHint, boolean forceFinish) {
         mPendingFinishCallbacks.add(callback);
         if (!forceFinish && mFinishRequested) {
             // If finish has already been requested, then add the callback to the pending list.
@@ -148,6 +164,7 @@
         // Finish not yet requested
         mFinishRequested = true;
         mFinishTargetIsLauncher = toRecents;
+        mLauncherIsVisibleAtFinish = launcherIsVisibleAtFinish;
         mOnFinishedListener.accept(this);
         Runnable finishCb = () -> {
             mController.finish(toRecents, sendUserLeaveHint, new IResultReceiver.Stub() {
@@ -224,6 +241,14 @@
         return mFinishTargetIsLauncher;
     }
 
+    /**
+     * RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether
+     * the animation was finished to launcher vs an app.
+     */
+    public boolean getLauncherIsVisibleAtFinish() {
+        return mLauncherIsVisibleAtFinish;
+    }
+
     public void dump(String prefix, PrintWriter pw) {
         pw.println(prefix + "RecentsAnimationController:");
 
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 59d4547..6a25ecb 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -55,6 +55,7 @@
 
 import androidx.annotation.MainThread;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
 
 import com.android.internal.logging.InstanceId;
@@ -70,6 +71,7 @@
 import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.RecentsAnimationListener;
@@ -1476,10 +1478,11 @@
     }
 
     /** Call shell to move a task with given `taskId` to desktop  */
-    public void moveToDesktop(int taskId, DesktopModeTransitionSource transitionSource) {
+    public void moveToDesktop(int taskId, DesktopModeTransitionSource transitionSource,
+            @Nullable RemoteTransition transition) {
         if (mDesktopMode != null) {
             try {
-                mDesktopMode.moveToDesktop(taskId, transitionSource);
+                mDesktopMode.moveToDesktop(taskId, transitionSource, transition);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call moveToDesktop", e);
             }
@@ -1634,4 +1637,24 @@
         pw.println("\tmUnfoldAnimationListener=" + mUnfoldAnimationListener);
         pw.println("\tmDragAndDrop=" + mDragAndDrop);
     }
+
+    /**
+     * Adds all interfaces held by this proxy to the bundle
+     */
+    @VisibleForTesting
+    public void addAllInterfaces(Bundle out) {
+        QuickStepContract.addInterface(mSystemUiProxy, out);
+        QuickStepContract.addInterface(mPip, out);
+        QuickStepContract.addInterface(mBubbles, out);
+        QuickStepContract.addInterface(mSysuiUnlockAnimationController, out);
+        QuickStepContract.addInterface(mSplitScreen, out);
+        QuickStepContract.addInterface(mOneHanded, out);
+        QuickStepContract.addInterface(mShellTransitions, out);
+        QuickStepContract.addInterface(mStartingWindow, out);
+        QuickStepContract.addInterface(mRecentTasks, out);
+        QuickStepContract.addInterface(mBackAnimation, out);
+        QuickStepContract.addInterface(mDesktopMode, out);
+        QuickStepContract.addInterface(mUnfoldAnimation, out);
+        QuickStepContract.addInterface(mDragAndDrop, out);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 0ea128a..2fcfa36 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -47,6 +47,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.fallback.window.RecentsWindowFactory;
 import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.quickstep.util.SystemUiFlagUtils;
@@ -65,10 +66,12 @@
             SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
 
     private final Context mCtx;
-    private RecentsWindowManager mRecentsWindowsManager;
+    private RecentsWindowFactory mRecentsWindowFactory;
     private RecentsAnimationController mController;
     private RecentsAnimationCallbacks mCallbacks;
     private RecentsAnimationTargets mTargets;
+    private RecentsAnimationDeviceState mDeviceState;
+
     // Temporary until we can hook into gesture state events
     private GestureState mLastGestureState;
     private RemoteAnimationTarget[] mLastAppearedTaskTargets;
@@ -100,9 +103,11 @@
         }
     };
 
-    TaskAnimationManager(Context ctx, RecentsWindowManager manager) {
+    TaskAnimationManager(Context ctx, RecentsWindowFactory recentsWindowFactory,
+            RecentsAnimationDeviceState deviceState) {
         mCtx = ctx;
-        mRecentsWindowsManager = manager;
+        mDeviceState = deviceState;
+        mRecentsWindowFactory = recentsWindowFactory;
     }
     SystemUiProxy getSystemUiProxy() {
         return SystemUiProxy.INSTANCE.get(mCtx);
@@ -298,7 +303,8 @@
                         || Flags.enableLauncherOverviewInWindow())) {
             mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent, options,
                     mCallbacks, gestureState.useSyntheticRecentsTransition());
-            mRecentsWindowsManager.startRecentsWindow(mCallbacks);
+            mRecentsWindowFactory.create(mDeviceState.getDisplayId())
+                    .startRecentsWindow(mCallbacks);
         } else {
             options.setPendingIntentBackgroundActivityStartMode(
                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 785666f..ab5e830 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -317,7 +317,7 @@
             boolean hideForExistingMultiWindow = container.getDeviceProfile().isMultiWindowMode;
             boolean isLargeTile = deviceProfile.isTablet && taskView.isLargeTile();
             boolean isTaskInExpectedScrollPosition =
-                    recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
+                    recentsView.isTaskInExpectedScrollPosition(taskView);
 
             if (notEnoughTasksToSplit || isTaskSplitNotSupported || hideForExistingMultiWindow
                     || (isLargeTile && isTaskInExpectedScrollPosition)) {
@@ -342,7 +342,7 @@
             final RecentsView recentsView = taskView.getRecentsView();
             boolean isLargeTile = deviceProfile.isTablet && taskView.isLargeTile();
             boolean isInExpectedScrollPosition =
-                    recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
+                    recentsView.isTaskInExpectedScrollPosition(taskView);
             boolean shouldShowActionsButtonInstead =
                     isLargeTile && isInExpectedScrollPosition;
 
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 783c87c..dec36cf 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -108,7 +108,7 @@
      * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
      */
     public static TaskView findTaskViewToLaunch(
-            RecentsView recentsView, View v, RemoteAnimationTarget[] targets) {
+            RecentsView<?, ?> recentsView, View v, RemoteAnimationTarget[] targets) {
         if (v instanceof TaskView) {
             TaskView taskView = (TaskView) v;
             return recentsView.isTaskViewVisible(taskView) ? taskView : null;
@@ -121,8 +121,7 @@
             ComponentName componentName = itemInfo.getTargetComponent();
             int userId = itemInfo.user.getIdentifier();
             if (componentName != null) {
-                for (int i = 0; i < recentsView.getTaskViewCount(); i++) {
-                    TaskView taskView = recentsView.getTaskViewAt(i);
+                for (TaskView taskView : recentsView.getTaskViews()) {
                     if (recentsView.isTaskViewVisible(taskView)) {
                         Task.TaskKey key = taskView.getFirstTask().key;
                         if (componentName.equals(key.getComponent()) && userId == key.userId) {
@@ -562,7 +561,7 @@
      * Start recents to desktop animation
      */
     public static AnimatorSet composeRecentsDesktopLaunchAnimator(
-            @NonNull DesktopTaskView launchingTaskView,
+            @NonNull TaskView launchingTaskView,
             @NonNull StateManager stateManager, @Nullable DepthController depthController,
             @NonNull TransitionInfo transitionInfo,
             SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
@@ -795,14 +794,15 @@
      * second applies to the target in the same index of the first.
      *
      * @param handles The handles wrapping each target.
+     * @param timestamp The start time of the current frame.
      * @param velocityPxPerMs The current velocity of the target animations.
      */
     @NonNull
     public static Pair<RemoteAnimationTarget[], WindowAnimationState[]> extractTargetsAndStates(
-            @NonNull RemoteTargetHandle[] handles, @NonNull PointF velocityPxPerMs) {
+            @NonNull RemoteTargetHandle[] handles, long timestamp,
+            @NonNull PointF velocityPxPerMs) {
         RemoteAnimationTarget[] targets = new RemoteAnimationTarget[handles.length];
         WindowAnimationState[] animationStates = new WindowAnimationState[handles.length];
-        long timestamp = System.currentTimeMillis();
 
         for (int i = 0; i < handles.length; i++) {
             targets[i] = handles[i].getTransformParams().getTargetSet().apps[i];
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 75e7c64..4ddbcdb 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -37,19 +37,6 @@
 import static com.android.quickstep.InputConsumerUtils.newConsumer;
 import static com.android.quickstep.InputConsumerUtils.tryCreateAssistantInputConsumer;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
-import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
 
 import android.app.PendingIntent;
 import android.app.Service;
@@ -96,13 +83,14 @@
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.PluginManagerWrapper;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.OverviewCommandHelper.CommandType;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
+import com.android.quickstep.fallback.window.RecentsWindowFactory;
 import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
 import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
 import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
@@ -148,7 +136,6 @@
 public class TouchInteractionService extends Service {
 
     private static final String SUBSTRING_PREFIX = "; ";
-    private static final String NEWLINE_PREFIX = "\n\t\t\t-> ";
 
     private static final String TAG = "TouchInteractionService";
 
@@ -173,30 +160,30 @@
         @BinderThread
         public void onInitialize(Bundle bundle) {
             ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
-                    bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
-            IPip pip = IPip.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_PIP));
-            IBubbles bubbles = IBubbles.Stub.asInterface(bundle.getBinder(KEY_EXTRA_SHELL_BUBBLES));
+                    bundle.getBinder(ISystemUiProxy.DESCRIPTOR));
+            IPip pip = IPip.Stub.asInterface(bundle.getBinder(IPip.DESCRIPTOR));
+            IBubbles bubbles = IBubbles.Stub.asInterface(bundle.getBinder(IBubbles.DESCRIPTOR));
             ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
-                    KEY_EXTRA_SHELL_SPLIT_SCREEN));
+                    ISplitScreen.DESCRIPTOR));
             IOneHanded onehanded = IOneHanded.Stub.asInterface(
-                    bundle.getBinder(KEY_EXTRA_SHELL_ONE_HANDED));
+                    bundle.getBinder(IOneHanded.DESCRIPTOR));
             IShellTransitions shellTransitions = IShellTransitions.Stub.asInterface(
-                    bundle.getBinder(KEY_EXTRA_SHELL_SHELL_TRANSITIONS));
+                    bundle.getBinder(IShellTransitions.DESCRIPTOR));
             IStartingWindow startingWindow = IStartingWindow.Stub.asInterface(
-                    bundle.getBinder(KEY_EXTRA_SHELL_STARTING_WINDOW));
+                    bundle.getBinder(IStartingWindow.DESCRIPTOR));
             ISysuiUnlockAnimationController launcherUnlockAnimationController =
                     ISysuiUnlockAnimationController.Stub.asInterface(
-                            bundle.getBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER));
+                            bundle.getBinder(ISysuiUnlockAnimationController.DESCRIPTOR));
             IRecentTasks recentTasks = IRecentTasks.Stub.asInterface(
-                    bundle.getBinder(KEY_EXTRA_SHELL_RECENT_TASKS));
+                    bundle.getBinder(IRecentTasks.DESCRIPTOR));
             IBackAnimation backAnimation = IBackAnimation.Stub.asInterface(
-                    bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION));
+                    bundle.getBinder(IBackAnimation.DESCRIPTOR));
             IDesktopMode desktopMode = IDesktopMode.Stub.asInterface(
-                    bundle.getBinder(KEY_EXTRA_SHELL_DESKTOP_MODE));
+                    bundle.getBinder(IDesktopMode.DESCRIPTOR));
             IUnfoldAnimation unfoldTransition = IUnfoldAnimation.Stub.asInterface(
-                    bundle.getBinder(KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER));
+                    bundle.getBinder(IUnfoldAnimation.DESCRIPTOR));
             IDragAndDrop dragAndDrop = IDragAndDrop.Stub.asInterface(
-                    bundle.getBinder(KEY_EXTRA_SHELL_DRAG_AND_DROP));
+                    bundle.getBinder(IDragAndDrop.DESCRIPTOR));
             MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
                 SystemUiProxy.INSTANCE.get(tis).setProxy(proxy, pip,
                         bubbles, splitscreen, onehanded, shellTransitions, startingWindow,
@@ -645,7 +632,7 @@
     private InputEventReceiver mInputEventReceiver;
 
     private TaskbarManager mTaskbarManager;
-    private RecentsWindowManager mRecentsWindowManager;
+    private RecentsWindowFactory mRecentsWindowFactory;
     private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = i -> null;
     private AllAppsActionManager mAllAppsActionManager;
     private InputManager mInputManager;
@@ -683,7 +670,7 @@
                 new DesktopAppLaunchTransitionManager(this, SystemUiProxy.INSTANCE.get(this));
         mDesktopAppLaunchTransitionManager.registerTransitions();
         if (Flags.enableLauncherOverviewInWindow() || Flags.enableFallbackOverviewInWindow()) {
-            mRecentsWindowManager = new RecentsWindowManager(this);
+            mRecentsWindowFactory = new RecentsWindowFactory(this);
         }
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
 
@@ -736,7 +723,7 @@
     public void onUserUnlocked() {
         Log.d(TAG, "onUserUnlocked: userId=" + getUserId()
                 + " instance=" + System.identityHashCode(this));
-        mTaskAnimationManager = new TaskAnimationManager(this, mRecentsWindowManager);
+        mTaskAnimationManager = new TaskAnimationManager(this, mRecentsWindowFactory, mDeviceState);
         mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
         mOverviewCommandHelper = new OverviewCommandHelper(this,
                 mOverviewComponentObserver, mTaskAnimationManager);
@@ -844,8 +831,8 @@
 
         mTaskbarManager.destroy();
 
-        if (mRecentsWindowManager != null) {
-            mRecentsWindowManager.destroy();
+        if (mRecentsWindowFactory != null) {
+            mRecentsWindowFactory.destroy();
         }
         if (mDesktopAppLaunchTransitionManager != null) {
             mDesktopAppLaunchTransitionManager.unregisterTransitions();
@@ -1288,20 +1275,20 @@
             GestureState gestureState, long touchTimeMs) {
         return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
                 gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
-                mInputConsumer);
+                mInputConsumer, MSDLPlayerWrapper.INSTANCE.get(this));
     }
 
     private AbsSwipeUpHandler createFallbackSwipeHandler(
             GestureState gestureState, long touchTimeMs) {
         return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
                 gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
-                mInputConsumer);
+                mInputConsumer, MSDLPlayerWrapper.INSTANCE.get(this));
     }
 
     private AbsSwipeUpHandler createRecentsWindowSwipeHandler(
             GestureState gestureState, long touchTimeMs) {
         return new RecentsWindowSwipeHandler(this, mDeviceState, mTaskAnimationManager,
                 gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
-                mInputConsumer, mRecentsWindowManager);
+                mInputConsumer, mRecentsWindowFactory, MSDLPlayerWrapper.INSTANCE.get(this));
     }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index daad6b7..28d95e2 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -73,13 +73,14 @@
     }
 
     public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr, getContainerInterface());
+        super(context, attrs, defStyleAttr);
         mContainer.getStateManager().addStateListener(this);
     }
 
-    private static BaseContainerInterface<RecentsState, ?> getContainerInterface() {
+    @Override
+    public BaseContainerInterface<RecentsState, ?> getContainerInterface(int displayId) {
         return (Flags.enableFallbackOverviewInWindow() || Flags.enableLauncherOverviewInWindow())
-                ? FallbackWindowInterface.getInstance()
+                ? FallbackWindowInterface.getInstance(displayId)
                 : FallbackActivityInterface.INSTANCE;
     }
 
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index 34783c7..c2e7536 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -45,6 +45,8 @@
     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 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);
@@ -61,6 +63,11 @@
             FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_RECENTS_VIEW_VISIBLE | FLAG_CLOSE_POPUPS
                     | FLAG_DISABLE_RESTORE);
 
+    /** Returns the corresponding RecentsState from ordinal provided */
+    public static RecentsState stateFromOrdinal(int ordinal) {
+        return sAllStates[ordinal];
+    }
+
     public final int ordinal;
     private final int mFlags;
 
@@ -70,6 +77,7 @@
     public RecentsState(int id, int flags) {
         this.ordinal = id;
         this.mFlags = flags;
+        sAllStates[id] = this;
     }
 
 
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowFactory.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowFactory.kt
new file mode 100644
index 0000000..ac0593e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowFactory.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.fallback.window
+
+import android.content.Context
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import android.util.Log
+import android.util.SparseArray
+import android.view.Display
+import androidx.core.util.valueIterator
+
+
+/**
+ * Factory for creating [RecentsWindowManager] instances based on context per display.
+ */
+class RecentsWindowFactory(private val context: Context) {
+
+    companion object {
+        private const val TAG = "RecentsWindowFactory"
+        private const val DEBUG = false
+    }
+
+    private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+    private val managerArray = SparseArray<RecentsWindowManager>()
+
+    private val displayListener: DisplayManager.DisplayListener =
+        (object : DisplayManager.DisplayListener {
+            override fun onDisplayAdded(displayId: Int) {
+                if (DEBUG) Log.d(TAG, "onDisplayAdded: displayId=$displayId")
+                create(displayId)
+            }
+
+            override fun onDisplayRemoved(displayId: Int) {
+                if (DEBUG) Log.d(TAG, "onDisplayRemoved: displayId=$displayId")
+                delete(displayId)
+            }
+
+            override fun onDisplayChanged(displayId: Int) {
+                if (DEBUG) Log.d(TAG, "onDisplayChanged: displayId=$displayId")
+            }
+        })
+
+    init {
+        create(Display.DEFAULT_DISPLAY) // create manager for first display early.
+        displayManager.registerDisplayListener(displayListener, Handler.getMain())
+    }
+
+    fun destroy() {
+        managerArray.valueIterator().forEach { manager ->
+            manager.destroy()
+        }
+        managerArray.clear()
+        displayManager.unregisterDisplayListener(displayListener)
+    }
+
+    fun get(displayId: Int): RecentsWindowManager? {
+        if (DEBUG) Log.d(TAG, "get: displayId=$displayId")
+        return managerArray[displayId]
+    }
+
+    fun delete(displayId: Int) {
+        if (DEBUG) Log.d(TAG, "delete: displayId=$displayId")
+        get(displayId)?.destroy()
+        managerArray.remove(displayId)
+    }
+
+    fun create(displayId: Int): RecentsWindowManager {
+        if (DEBUG) Log.d(TAG, "create: displayId=$displayId")
+        get(displayId)?.let {
+            return it
+        }
+        val display = displayManager.getDisplay(displayId)
+        val displayContext = context.createDisplayContext(display)
+        val recentsWindowManager = RecentsWindowManager(displayId, displayContext)
+        managerArray[displayId] = recentsWindowManager
+        return recentsWindowManager
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
index 86f9829..b3cc3e9 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
@@ -42,6 +42,7 @@
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory
 import com.android.launcher3.statemanager.StatefulContainer
 import com.android.launcher3.taskbar.TaskbarUIController
+import com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL
 import com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL
 import com.android.launcher3.util.ContextTracker
 import com.android.launcher3.util.DisplayController
@@ -88,7 +89,7 @@
  * To add new protologs, see [RecentsWindowProtoLogProxy]. To enable logging to logcat, see
  * [QuickstepProtoLogGroup.Constants.DEBUG_RECENTS_WINDOW]
  */
-class RecentsWindowManager(context: Context) :
+class RecentsWindowManager(private val displayId: Int, context: Context) :
     RecentsWindowContext(context), RecentsViewContainer, StatefulContainer<RecentsState> {
 
     companion object {
@@ -111,7 +112,7 @@
     private var layoutInflater: LayoutInflater = LayoutInflater.from(this).cloneInContext(this)
     private var stateManager: StateManager<RecentsState, RecentsWindowManager> =
         StateManager<RecentsState, RecentsWindowManager>(this, RecentsState.BG_LAUNCHER)
-    private var mSystemUiController: SystemUiController? = null
+    private var systemUiController: SystemUiController? = null
 
     private var dragLayer: RecentsDragLayer<RecentsWindowManager>? = null
     private var windowView: View? = null
@@ -124,7 +125,7 @@
     private var tisBindHelper: TISBindHelper = TISBindHelper(this) {}
 
     // Callback array that corresponds to events defined in @ActivityEvent
-    private val mEventCallbacks =
+    private val eventCallbacks =
         listOf(RunnableList(), RunnableList(), RunnableList(), RunnableList())
     private var onInitListener: Predicate<Boolean>? = null
 
@@ -151,14 +152,14 @@
         }
 
     init {
-        FallbackWindowInterface.init(this)
+        FallbackWindowInterface.init(displayId, this)
         TaskStackChangeListeners.getInstance().registerTaskStackListener(taskStackChangeListener)
     }
 
     override fun destroy() {
         super.destroy()
         cleanupRecentsWindow()
-        FallbackWindowInterface.getInstance()?.destroy()
+        FallbackWindowInterface.getInstance(displayId)?.destroy()
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(taskStackChangeListener)
         callbacks?.removeListener(recentsAnimationListener)
         recentsWindowTracker.onContextDestroyed(this)
@@ -182,7 +183,7 @@
     }
 
     private fun startHomeInternal() {
-        val runner = LauncherAnimationRunner(mainThreadHandler, mAnimationToHomeFactory, true)
+        val runner = LauncherAnimationRunner(mainThreadHandler, animationToHomeFactory, true)
         val options =
             ActivityOptions.makeRemoteAnimation(
                 RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0),
@@ -196,7 +197,7 @@
         stateManager.moveToRestState()
     }
 
-    private val mAnimationToHomeFactory =
+    private val animationToHomeFactory =
         RemoteAnimationFactory {
             _: Int,
             appTargets: Array<RemoteAnimationTarget>?,
@@ -287,7 +288,7 @@
         actionsView?.updateDimension(getDeviceProfile(), recentsView?.lastComputedTaskSize)
         actionsView?.updateVerticalMargin(DisplayController.getNavigationMode(this))
 
-        mSystemUiController = SystemUiController(windowView)
+        systemUiController = SystemUiController(windowView)
         recentsWindowTracker.handleCreate(this)
 
         this.callbacks = callbacks
@@ -357,8 +358,11 @@
         if (state == HOME || state == BG_LAUNCHER) {
             cleanupRecentsWindow()
         }
-        if (state === DEFAULT) {
-            AccessibilityManagerCompat.sendStateEventToTest(baseContext, OVERVIEW_STATE_ORDINAL)
+        when (state) {
+            HOME ->
+                AccessibilityManagerCompat.sendStateEventToTest(baseContext, NORMAL_STATE_ORDINAL)
+            DEFAULT ->
+                AccessibilityManagerCompat.sendStateEventToTest(baseContext, OVERVIEW_STATE_ORDINAL)
         }
     }
 
@@ -376,10 +380,10 @@
     }
 
     override fun getSystemUiController(): SystemUiController? {
-        if (mSystemUiController == null) {
-            mSystemUiController = SystemUiController(rootView)
+        if (systemUiController == null) {
+            systemUiController = SystemUiController(rootView)
         }
-        return mSystemUiController
+        return systemUiController
     }
 
     override fun getContext(): Context {
@@ -430,12 +434,12 @@
 
     /** Adds a callback for the provided activity event */
     override fun addEventCallback(@BaseActivity.ActivityEvent event: Int, callback: Runnable?) {
-        mEventCallbacks[event].add(callback)
+        eventCallbacks[event].add(callback)
     }
 
     /** Removes a previously added callback */
     override fun removeEventCallback(@BaseActivity.ActivityEvent event: Int, callback: Runnable?) {
-        mEventCallbacks[event].remove(callback)
+        eventCallbacks[event].remove(callback)
     }
 
     override fun runOnBindToTouchInteractionService(r: Runnable?) {
@@ -457,7 +461,7 @@
     }
 
     override fun isRecentsViewVisible(): Boolean {
-        return getStateManager().state!!.isRecentsViewVisible
+        return isShowing() || getStateManager().state!!.isRecentsViewVisible
     }
 
     override fun createAtomicAnimationFactory(): AtomicAnimationFactory<RecentsState?>? {
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
index be71385..4a08d12 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
@@ -62,6 +62,7 @@
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.quickstep.AbsSwipeUpHandler;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsAnimationController;
@@ -110,9 +111,9 @@
     public RecentsWindowSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
             boolean continuingLastGesture, InputConsumerController inputConsumer,
-            RecentsWindowManager recentsWindowManager) {
+            RecentsWindowFactory recentsWindowFactory, MSDLPlayerWrapper msdlPlayerWrapper) {
         super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
-                continuingLastGesture, inputConsumer, recentsWindowManager);
+                continuingLastGesture, inputConsumer, recentsWindowFactory, msdlPlayerWrapper);
 
         mRunningOverHome = mGestureState.getRunningTask() != null
                 && mGestureState.getRunningTask().isHomeTask();
@@ -159,7 +160,10 @@
         boolean fromHomeToHome = mRunningOverHome
                 && endTarget == GestureState.GestureEndTarget.HOME;
         if (fromHomeToHome) {
-            mRecentsWindowManager.startHome(/* finishRecentsAnimation= */ false);
+            RecentsWindowManager manager = mRecentsWindowFactory.get(mDeviceState.getDisplayId());
+            if (manager != null) {
+                manager.startHome(/* finishRecentsAnimation= */ false);
+            }
         }
         super.animateGestureEnd(
                 startShift,
@@ -220,7 +224,11 @@
             // the PiP task appearing.
             recentsCallback = () -> {
                 callback.run();
-                mRecentsWindowManager.startHome();
+                RecentsWindowManager manager =
+                        mRecentsWindowFactory.get(mDeviceState.getDisplayId());
+                if (manager != null) {
+                    manager.startHome();
+                }
             };
         } else {
             recentsCallback = callback;
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
index 3b59864..53969c5 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
@@ -37,6 +37,12 @@
     fun getThumbnailById(taskId: Int): Flow<ThumbnailData?>
 
     /**
+     * Gets the [ThumbnailData] associated with a task that has id [taskId]. Flow will settle on
+     * null if the task was not found or is invisible.
+     */
+    fun getCurrentThumbnailById(taskId: Int): ThumbnailData?
+
+    /**
      * Sets the tasks that are visible, indicating that properties relating to visuals need to be
      * populated e.g. icons/thumbnails etc.
      */
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 6c627ef..3aae760 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -72,6 +72,8 @@
     override fun getThumbnailById(taskId: Int) =
         getTaskDataById(taskId).map { it?.thumbnail }.distinctUntilChangedBy { it?.snapshotId }
 
+    override fun getCurrentThumbnailById(taskId: Int) = tasks.value[taskId]?.thumbnail
+
     override fun setVisibleTasks(visibleTaskIdList: Set<Int>) {
         val tasksNoLongerVisible = taskRequests.keys.subtract(visibleTaskIdList)
         val newlyVisibleTasks = visibleTaskIdList.subtract(taskRequests.keys)
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
index f060d7d..bea1d07 100644
--- a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
+++ b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
@@ -24,18 +24,17 @@
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
-import kotlinx.coroutines.flow.firstOrNull
 
 /** Use case for retrieving [Matrix] for positioning Thumbnail in a View */
 class GetThumbnailPositionUseCase(
     private val deviceProfileRepository: RecentsDeviceProfileRepository,
     private val rotationStateRepository: RecentsRotationStateRepository,
     private val tasksRepository: RecentTasksRepository,
-    private val previewPositionHelper: PreviewPositionHelper = PreviewPositionHelper()
+    private val previewPositionHelper: PreviewPositionHelper = PreviewPositionHelper(),
 ) {
-    suspend fun run(taskId: Int, width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
+    fun run(taskId: Int, width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
         val thumbnailData =
-            tasksRepository.getThumbnailById(taskId).firstOrNull() ?: return MissingThumbnail
+            tasksRepository.getCurrentThumbnailById(taskId) ?: return MissingThumbnail
         val thumbnail = thumbnailData.thumbnail ?: return MissingThumbnail
         previewPositionHelper.updateThumbnailMatrix(
             Rect(0, 0, thumbnail.width, thumbnail.height),
@@ -44,11 +43,11 @@
             height,
             deviceProfileRepository.getRecentsDeviceProfile().isLargeScreen,
             rotationStateRepository.getRecentsRotationState().activityRotation,
-            isRtl
+            isRtl,
         )
         return MatrixScaling(
             previewPositionHelper.matrix,
-            previewPositionHelper.isOrientationChanged
+            previewPositionHelper.isOrientationChanged,
         )
     }
 }
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
index 3aa808e..b9e9e02 100644
--- a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
+++ b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
@@ -18,13 +18,9 @@
 
 import android.graphics.Bitmap
 import com.android.quickstep.recents.data.RecentTasksRepository
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.runBlocking
 
 /** Use case for retrieving thumbnail. */
 class GetThumbnailUseCase(private val taskRepository: RecentTasksRepository) {
     /** Returns the latest thumbnail associated with [taskId] if loaded, or null otherwise */
-    fun run(taskId: Int): Bitmap? = runBlocking {
-        taskRepository.getThumbnailById(taskId).firstOrNull()?.thumbnail
-    }
+    fun run(taskId: Int): Bitmap? = taskRepository.getCurrentThumbnailById(taskId)?.thumbnail
 }
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt
index 1d19c7d..5be5f4a 100644
--- a/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt
+++ b/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt
@@ -22,14 +22,11 @@
 import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
 import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
 import com.android.quickstep.recents.data.RecentTasksRepository
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.runBlocking
 
 /** UseCase to calculate flags for status bar and navigation bar */
 class SysUiStatusNavFlagsUseCase(private val taskRepository: RecentTasksRepository) {
     fun getSysUiStatusNavFlags(taskId: Int): Int {
-        val thumbnailData =
-            runBlocking { taskRepository.getThumbnailById(taskId).firstOrNull() } ?: return 0
+        val thumbnailData = taskRepository.getCurrentThumbnailById(taskId) ?: return 0
 
         val thumbnailAppearance = thumbnailData.appearance
         var flags = 0
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 0c783d3..ea4602d 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -24,8 +24,8 @@
 import android.util.Log
 import android.view.View
 import android.view.ViewOutlineProvider
+import android.widget.FrameLayout
 import androidx.annotation.ColorInt
-import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.isInvisible
 import com.android.launcher3.R
 import com.android.launcher3.util.ViewPool
@@ -46,7 +46,7 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 
-class TaskThumbnailView : ConstraintLayout, ViewPool.Reusable {
+class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
     private lateinit var viewData: TaskThumbnailViewData
     private lateinit var viewModel: TaskThumbnailViewModel
 
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
index 4e13d1c..14359db 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
@@ -28,7 +28,6 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.runBlocking
 
 /** View model for TaskOverlay */
 class TaskOverlayViewModel(
@@ -41,7 +40,7 @@
         combine(
                 recentsViewData.overlayEnabled,
                 recentsViewData.settledFullyVisibleTaskIds.map { it.contains(task.key.id) },
-                recentTasksRepository.getThumbnailById(task.key.id)
+                recentTasksRepository.getThumbnailById(task.key.id),
             ) { isOverlayEnabled, isFullyVisible, thumbnailData ->
                 if (isOverlayEnabled && isFullyVisible) {
                     Enabled(
@@ -55,24 +54,22 @@
             .distinctUntilChanged()
 
     fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
-        return runBlocking {
-            val matrix: Matrix
-            val isRotated: Boolean
-            when (
-                val thumbnailPositionState =
-                    getThumbnailPositionUseCase.run(task.key.id, width, height, isRtl)
-            ) {
-                is MatrixScaling -> {
-                    matrix = thumbnailPositionState.matrix
-                    isRotated = thumbnailPositionState.isRotated
-                }
-                is MissingThumbnail -> {
-                    matrix = Matrix.IDENTITY_MATRIX
-                    isRotated = false
-                }
+        val matrix: Matrix
+        val isRotated: Boolean
+        when (
+            val thumbnailPositionState =
+                getThumbnailPositionUseCase.run(task.key.id, width, height, isRtl)
+        ) {
+            is MatrixScaling -> {
+                matrix = thumbnailPositionState.matrix
+                isRotated = thumbnailPositionState.isRotated
             }
-            ThumbnailPositionState(matrix, isRotated)
+            is MissingThumbnail -> {
+                matrix = Matrix.IDENTITY_MATRIX
+                isRotated = false
+            }
         }
+        return ThumbnailPositionState(matrix, isRotated)
     }
 
     data class ThumbnailPositionState(val matrix: Matrix, val isRotated: Boolean)
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
index b6cb984..b5b2fc9 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
@@ -44,7 +44,6 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.runBlocking
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class TaskThumbnailViewModelImpl(
@@ -61,12 +60,14 @@
 
     override val dimProgress: Flow<Float> =
         combine(taskContainerData.taskMenuOpenProgress, recentsViewData.tintAmount) {
-            taskMenuOpenProgress,
-            tintAmount ->
-            max(taskMenuOpenProgress * MAX_SCRIM_ALPHA, tintAmount)
-        }
+                taskMenuOpenProgress,
+                tintAmount ->
+                max(taskMenuOpenProgress * MAX_SCRIM_ALPHA, tintAmount)
+            }
+            .flowOn(dispatcherProvider.background)
 
-    override val splashAlpha = splashProgress.flatMapLatest { it }
+    override val splashAlpha =
+        splashProgress.flatMapLatest { it }.flowOn(dispatcherProvider.background)
 
     private val isLiveTile =
         combine(
@@ -77,7 +78,6 @@
                 runningTaskIds.contains(taskId) && !runningTaskShowScreenshot
             }
             .distinctUntilChanged()
-            .flowOn(dispatcherProvider.default)
 
     override val uiState: Flow<TaskThumbnailUiState> =
         combine(task.flatMapLatest { it }, isLiveTile) { taskVal, isRunning ->
@@ -99,7 +99,7 @@
                 }
             }
             .distinctUntilChanged()
-            .flowOn(dispatcherProvider.default)
+            .flowOn(dispatcherProvider.background)
 
     override fun bind(taskId: Int) {
         Log.d(TAG, "bind taskId: $taskId")
@@ -108,17 +108,14 @@
         splashProgress.value = splashAlphaUseCase.execute(taskId)
     }
 
-    override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix {
-        return runBlocking {
-            when (
-                val thumbnailPositionState =
-                    getThumbnailPositionUseCase.run(taskId, width, height, isRtl)
-            ) {
-                is ThumbnailPositionState.MatrixScaling -> thumbnailPositionState.matrix
-                is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
-            }
+    override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix =
+        when (
+            val thumbnailPositionState =
+                getThumbnailPositionUseCase.run(taskId, width, height, isRtl)
+        ) {
+            is ThumbnailPositionState.MatrixScaling -> thumbnailPositionState.matrix
+            is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
         }
-    }
 
     private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
 
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 1312aa4..3a0324c 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -28,8 +28,7 @@
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.getIndex;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.isPersistentSnapPosition;
 
 import android.content.Context;
@@ -55,6 +54,7 @@
 import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.TaskViewItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -74,6 +74,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
@@ -171,20 +172,6 @@
      */
     public void saveAppPair(GroupedTaskView gtv) {
         InteractionJankMonitorWrapper.begin(gtv, Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
-        List<TaskContainer> containers = gtv.getTaskContainers();
-        WorkspaceItemInfo recentsInfo1 = containers.get(0).getItemInfo();
-        WorkspaceItemInfo recentsInfo2 = containers.get(1).getItemInfo();
-        WorkspaceItemInfo app1 = resolveAppPairWorkspaceInfo(recentsInfo1);
-        WorkspaceItemInfo app2 = resolveAppPairWorkspaceInfo(recentsInfo2);
-
-        if (app1 == null || app2 == null) {
-            // This shouldn't happen if canSaveAppPair() is called above, but log an error and do
-            // not create the app pair if the workspace items can't be resolved
-            Log.w(TAG, "Failed to save app pair due to invalid apps ("
-                    + "app1=" + recentsInfo1.getComponentKey().componentName
-                    + " app2=" + recentsInfo2.getComponentKey().componentName + ")");
-            return;
-        }
 
         @PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
         if (snapPosition == SNAP_TO_NONE) {
@@ -198,9 +185,30 @@
             return;
         }
 
-        app1.rank = encodeRank(SPLIT_POSITION_TOP_OR_LEFT, snapPosition);
-        app2.rank = encodeRank(SPLIT_POSITION_BOTTOM_OR_RIGHT, snapPosition);
-        AppPairInfo newAppPair = new AppPairInfo(app1, app2);
+        List<TaskContainer> containers = gtv.getTaskContainers();
+        List<TaskViewItemInfo> recentsInfos =
+                containers.stream().map(TaskContainer::getItemInfo).toList();
+        List<WorkspaceItemInfo> apps =
+                recentsInfos.stream().map(this::resolveAppPairWorkspaceInfo).toList();
+
+        if (apps.stream().anyMatch(Objects::isNull)) {
+            // This shouldn't happen if canSaveAppPair() is called above, but log an error and do
+            // not create the app pair if the workspace items can't be resolved
+            StringBuilder error =
+                    new StringBuilder("Failed to save app pair due to invalid apps (");
+            for (int i = 0; i < recentsInfos.size(); i++) {
+                error.append("app").append(i).append("=")
+                        .append(recentsInfos.get(i).getComponentKey().componentName).append(" ");
+            }
+            error.append(")");
+            Log.w(TAG, error.toString());
+            return;
+        }
+
+        for (int i = 0; i < apps.size(); i++) {
+            apps.get(i).rank = encodeRank(getIndex(i), snapPosition);
+        }
+        AppPairInfo newAppPair = new AppPairInfo(apps);
 
         IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
         MODEL_EXECUTOR.execute(() -> {
diff --git a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
index c3b072d..7393de4 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
@@ -48,16 +48,6 @@
         return otherTasks + desktopTasks
     }
 
-    /** Returns the expected index of the focus task. */
-    fun getFocusedTaskIndex(taskGroups: List<GroupTask>): Int {
-        // The focused task index is placed after the desktop tasks views.
-        return if (enableLargeDesktopWindowingTile()) {
-            taskGroups.count { it.taskViewType == TaskViewType.DESKTOP }
-        } else {
-            0
-        }
-    }
-
     /** Counts [TaskView]s that are [DesktopTaskView] instances. */
     fun getDesktopTaskViewCount(taskViews: Iterable<TaskView>): Int =
         taskViews.count { it is DesktopTaskView }
@@ -81,6 +71,33 @@
     ): TaskView? =
         taskViews.firstOrNull { it.isLargeTile && !(splitSelectActive && it is DesktopTaskView) }
 
+    /** Returns the expected focus task. */
+    fun getExpectedFocusedTask(taskViews: Iterable<TaskView>): TaskView? =
+        if (enableLargeDesktopWindowingTile()) taskViews.firstOrNull { it !is DesktopTaskView }
+        else taskViews.firstOrNull()
+
+    /**
+     * Returns the [TaskView] that should be the current page during task binding, in the following
+     * priorities:
+     * 1. Running task
+     * 2. Focused task
+     * 3. First non-desktop task
+     * 4. Last desktop task
+     * 5. null otherwise
+     */
+    fun getExpectedCurrentTask(
+        runningTaskView: TaskView?,
+        focusedTaskView: TaskView?,
+        taskViews: Iterable<TaskView>,
+    ): TaskView? =
+        runningTaskView
+            ?: focusedTaskView
+            ?: taskViews.firstOrNull { it !is DesktopTaskView }
+            ?: taskViews.lastOrNull()
+
+    /** Returns the first TaskView if it exists, or null otherwise. */
+    fun getFirstTaskView(taskViews: Iterable<TaskView>): TaskView? = taskViews.firstOrNull()
+
     /**
      * Returns the first TaskView that is not large
      *
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index d35a36a..1eb91ae 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -106,6 +106,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import java.util.function.Consumer;
 
 /**
@@ -941,6 +942,10 @@
                         mLauncher, mLauncher.getDragLayer(),
                         null /* thumbnail */,
                         mAppIcon, new RectF());
+                floatingTaskView.setOnClickListener(view ->
+                        getSplitAnimationController()
+                                .playAnimPlaceholderToFullscreen(mContainer, view,
+                                        Optional.of(() -> resetState())));
                 floatingTaskView.setAlpha(1);
                 floatingTaskView.addStagingAnimation(anim, mTaskBounds, mTempRect,
                         false /* fadeWithThumbnail */, true /* isStagedTask */);
diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
new file mode 100644
index 0000000..f973dd0
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.ImageButton
+
+/**
+ * Button for supporting multiple desktop sessions. The button will be next to the first TaskView
+ * inside overview, while clicking this button will create a new desktop session.
+ */
+class AddDesktopButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+    ImageButton(context, attrs) {
+    // TODO(b/382057498): add this button the overview.
+}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 576a56e..7d04451 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -24,8 +24,10 @@
 import android.util.Log
 import android.view.Gravity
 import android.view.View
+import android.view.ViewStub
 import androidx.core.content.res.ResourcesCompat
 import androidx.core.view.updateLayoutParams
+import com.android.launcher3.Flags.enableOverviewIconMenu
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
 import com.android.launcher3.R
 import com.android.launcher3.testing.TestLogging
@@ -85,7 +87,7 @@
     override fun onFinishInflate() {
         super.onFinishInflate()
         iconView =
-            getOrInflateIconView(R.id.icon).apply {
+            (findViewById<View>(R.id.icon) as TaskViewIcon).apply {
                 setIcon(
                     this,
                     ResourcesCompat.getDrawable(
@@ -109,6 +111,16 @@
             }
     }
 
+    override fun inflateViewStubs() {
+        findViewById<ViewStub>(R.id.icon)
+            ?.apply {
+                layoutResource =
+                    if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+                    else R.layout.icon_view
+            }
+            ?.inflate()
+    }
+
     /** Updates this desktop task to the gives task list defined in `tasks` */
     fun bind(
         tasks: List<Task>,
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 0d9583d..2e94534 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -21,8 +21,10 @@
 import android.util.AttributeSet
 import android.util.Log
 import android.view.View
+import android.view.ViewStub
 import com.android.internal.jank.Cuj
 import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
 import com.android.launcher3.R
 import com.android.launcher3.Utilities
 import com.android.launcher3.config.FeatureFlags
@@ -82,6 +84,24 @@
         }
     }
 
+    override fun inflateViewStubs() {
+        super.inflateViewStubs()
+        findViewById<ViewStub>(R.id.bottomright_snapshot)
+            ?.apply {
+                layoutResource =
+                    if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail
+                    else R.layout.task_thumbnail_deprecated
+            }
+            ?.inflate()
+        findViewById<ViewStub>(R.id.bottomRight_icon)
+            ?.apply {
+                layoutResource =
+                    if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+                    else R.layout.icon_view
+            }
+            ?.inflate()
+    }
+
     override fun onRecycle() {
         super.onRecycle()
         splitBoundsConfig = null
diff --git a/quickstep/src/com/android/quickstep/views/IconView.kt b/quickstep/src/com/android/quickstep/views/IconView.kt
index 583207f..3ee12ab 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconView.kt
@@ -25,10 +25,13 @@
 import android.widget.FrameLayout
 import androidx.core.view.updateLayoutParams
 import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags
 import com.android.launcher3.Utilities
+import com.android.launcher3.util.MSDLPlayerWrapper
 import com.android.launcher3.util.MultiValueAlpha
 import com.android.launcher3.views.ActivityContext
 import com.android.quickstep.util.RecentsOrientedState
+import com.google.android.msdl.data.model.MSDLToken
 
 /**
  * A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout
@@ -39,19 +42,32 @@
     private var drawable: Drawable? = null
     private var drawableWidth = 0
     private var drawableHeight = 0
+    private var msdlPlayerWrapper: MSDLPlayerWrapper? = null
 
-    constructor(context: Context) : super(context)
+    constructor(context: Context) : super(context) {
+        msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
+    }
 
-    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
+        msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
+    }
 
     constructor(
         context: Context,
         attrs: AttributeSet?,
         defStyleAttr: Int,
-    ) : super(context, attrs, defStyleAttr)
+    ) : super(context, attrs, defStyleAttr) {
+        msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
+    }
 
     init {
         multiValueAlpha.setUpdateVisibility(true)
+        // Haptics are handled by the MSDLPlayerWrapper
+        isHapticFeedbackEnabled = !Flags.msdlFeedback() || msdlPlayerWrapper == null
+    }
+
+    override fun setOnLongClickListener(l: OnLongClickListener?) {
+        super.setOnLongClickListener(l?.withFeedback())
     }
 
     /** Sets a [Drawable] to be displayed. */
@@ -136,7 +152,7 @@
             taskIconMargin = deviceProfile.overviewTaskMarginPx,
             taskIconHeight = deviceProfile.overviewTaskIconSizePx,
             thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx,
-            isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
+            isRtl = layoutDirection == LAYOUT_DIRECTION_RTL,
         )
         updateLayoutParams<FrameLayout.LayoutParams> {
             height = deviceProfile.overviewTaskIconSizePx
@@ -151,6 +167,16 @@
 
     override fun asView(): View = this
 
+    private fun OnLongClickListener.withFeedback(): OnLongClickListener {
+        val delegate = this
+        return OnLongClickListener { v: View ->
+            if (Flags.msdlFeedback()) {
+                msdlPlayerWrapper?.playToken(MSDLToken.LONG_PRESS)
+            }
+            delegate.onLongClick(v)
+        }
+    }
+
     companion object {
         private const val NUM_ALPHA_CHANNELS = 2
         private const val INDEX_CONTENT_ALPHA = 0
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index bbb8cc8..4f823e6 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -47,6 +47,7 @@
 import com.android.launcher3.util.PendingSplitSelectInfo;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
+import com.android.quickstep.BaseContainerInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.RotationTouchHelper;
@@ -73,7 +74,7 @@
     }
 
     public LauncherRecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr, LauncherActivityInterface.INSTANCE);
+        super(context, attrs, defStyleAttr);
         getStateManager().addStateListener(this);
     }
 
@@ -225,6 +226,11 @@
     }
 
     @Override
+    protected BaseContainerInterface<LauncherState, ?> getContainerInterface(int displayId) {
+        return LauncherActivityInterface.INSTANCE;
+    }
+
+    @Override
     protected void onDismissAnimationEnds() {
         super.onDismissAnimationEnds();
         if (mContainer.isInState(OVERVIEW_SPLIT_SELECT)) {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 6fc33dc..3b46367 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -503,7 +503,7 @@
     private static final float FOREGROUND_SCRIM_TINT = 0.32f;
 
     protected final RecentsOrientedState mOrientationState;
-    protected final BaseContainerInterface<STATE_TYPE, CONTAINER_TYPE> mSizeStrategy;
+    protected final BaseContainerInterface<STATE_TYPE, ?> mSizeStrategy;
     @Nullable
     protected RecentsAnimationController mRecentsAnimationController;
     @Nullable
@@ -691,7 +691,7 @@
     protected boolean mRunningTaskTileHidden;
     protected int mFocusedTaskViewId = INVALID_TASK_ID;
 
-    private boolean mTaskIconScaledDown = false;
+    private boolean mTaskIconVisible = true;
     private boolean mRunningTaskShowScreenshot = false;
     private float mRunningTaskAttachAlpha;
 
@@ -846,13 +846,18 @@
 
     private final Matrix mTmpMatrix = new Matrix();
 
-    public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-            BaseContainerInterface sizeStrategy) {
+    @Nullable
+    public TaskView getFirstTaskView() {
+        return mUtils.getFirstTaskView(getTaskViews());
+    }
+
+    public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         setEnableFreeScroll(true);
 
-        mSizeStrategy = sizeStrategy;
         mContainer = RecentsViewContainer.containerFromContext(context);
+        mSizeStrategy = getContainerInterface(mContainer.getDisplayId());
+
         mOrientationState = new RecentsOrientedState(
                 context, mSizeStrategy, this::animateRecentsRotationInPlace);
         final int rotation = mContainer.getDisplay().getRotation();
@@ -1244,24 +1249,21 @@
         // - It's the focused task to be moved to the front, we immediately re-add the task
         if (child instanceof TaskView && child != mSplitHiddenTaskView
                 && child != mMovingTaskView) {
-            clearAndRecycleTaskView((TaskView) child);
+            TaskView taskView = (TaskView) child;
+            for (int i : taskView.getTaskIds()) {
+                mHasVisibleTaskData.delete(i);
+            }
+            if (child instanceof GroupedTaskView) {
+                mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
+            } else if (child instanceof DesktopTaskView) {
+                mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
+            } else {
+                mTaskViewPool.recycle(taskView);
+            }
+            mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
         }
     }
 
-    private void clearAndRecycleTaskView(TaskView taskView) {
-        for (int taskId : taskView.getTaskIds()) {
-            mHasVisibleTaskData.delete(taskId);
-        }
-        if (taskView instanceof GroupedTaskView) {
-            mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
-        } else if (taskView instanceof DesktopTaskView) {
-            mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
-        } else {
-            mTaskViewPool.recycle(taskView);
-        }
-        mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
-    }
-
     @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
@@ -1496,17 +1498,29 @@
     }
 
     /**
-     * Returns true if the task is in expected scroll position.
-     *
-     * @param taskIndex the index of the task
+     * Returns true if the given TaskView is in expected scroll position.
      */
-    public boolean isTaskInExpectedScrollPosition(int taskIndex) {
-        return getScrollForPage(taskIndex) == getPagedOrientationHandler().getPrimaryScroll(this);
+    public boolean isTaskInExpectedScrollPosition(TaskView taskView) {
+        return getScrollForPage(indexOfChild(taskView))
+                == getPagedOrientationHandler().getPrimaryScroll(this);
     }
 
     private boolean isFocusedTaskInExpectedScrollPosition() {
         TaskView focusedTask = getFocusedTaskView();
-        return focusedTask != null && isTaskInExpectedScrollPosition(indexOfChild(focusedTask));
+        return focusedTask != null && isTaskInExpectedScrollPosition(focusedTask);
+    }
+
+    /**
+     * Launch DesktopTaskView if found.
+     * @return provides runnable list to attach runnable at end of Desktop Mode launch
+     */
+    public RunnableList launchDesktopTaskView() {
+        for (TaskView taskView : getTaskViews()) {
+            if (taskView instanceof DesktopTaskView) {
+                return taskView.launchWithAnimation();
+            }
+        }
+        return null;
     }
 
     /**
@@ -1957,18 +1971,20 @@
         }
 
         // Keep same previous focused task
-        TaskView newFocusedTaskView = getTaskViewByTaskIds(focusedTaskIds);
-        if (enableLargeDesktopWindowingTile() && newFocusedTaskView instanceof DesktopTaskView) {
-            newFocusedTaskView = null;
+        TaskView newFocusedTaskView = null;
+        if (!enableGridOnlyOverview()) {
+            newFocusedTaskView = getTaskViewByTaskIds(focusedTaskIds);
+            if (enableLargeDesktopWindowingTile()
+                    && newFocusedTaskView instanceof DesktopTaskView) {
+                newFocusedTaskView = null;
+            }
+            // If the list changed, maybe the focused task doesn't exist anymore.
+            if (newFocusedTaskView == null) {
+                newFocusedTaskView = mUtils.getExpectedFocusedTask(getTaskViews());
+            }
         }
-        // If the list changed, maybe the focused task doesn't exist anymore
-        int newFocusedTaskViewIndex = mUtils.getFocusedTaskIndex(taskGroups);
-        if (newFocusedTaskView == null && getTaskViewCount() > newFocusedTaskViewIndex) {
-            newFocusedTaskView = getTaskViewAt(newFocusedTaskViewIndex);
-        }
-
-        setFocusedTaskViewId(newFocusedTaskView != null && !enableGridOnlyOverview()
-                ? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID);
+        setFocusedTaskViewId(
+                newFocusedTaskView != null ? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID);
 
         updateTaskSize();
         updateChildTaskOrientations();
@@ -1987,6 +2003,7 @@
                     // for cases where the running task isn't included in this load plan (e.g. if
                     // the current running task is excludedFromRecents.)
                     showCurrentTask(mActiveGestureRunningTasks, "applyLoadPlan");
+                    newRunningTaskView = getRunningTaskView();
                 } else {
                     setRunningTaskViewId(INVALID_TASK_ID);
                 }
@@ -2006,12 +2023,9 @@
         } else if (previousFocusedPage != INVALID_PAGE) {
             targetPage = previousFocusedPage;
         } else {
-            // Set the current page to the running task, but not if settling on new task.
-            if (hasAllValidTaskIds(runningTaskIds)) {
-                targetPage = indexOfChild(newRunningTaskView);
-            } else if (getTaskViewCount() > newFocusedTaskViewIndex) {
-                targetPage = indexOfChild(requireTaskViewAt(newFocusedTaskViewIndex));
-            }
+            targetPage = indexOfChild(
+                    mUtils.getExpectedCurrentTask(newRunningTaskView, newFocusedTaskView,
+                            getTaskViews()));
         }
         if (targetPage != -1 && mCurrentPage != targetPage) {
             int finalTargetPage = targetPage;
@@ -2107,7 +2121,7 @@
             if (Arrays.stream(taskView.getTaskIds()).noneMatch(
                     taskId -> taskId == mIgnoreResetTaskId)) {
                 taskView.resetViewTransforms();
-                taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
+                taskView.setIconVisibleForGesture(mTaskIconVisible);
                 taskView.setStableAlpha(mContentAlpha);
                 taskView.setFullscreenProgress(mFullscreenProgress);
                 taskView.setModalness(mTaskModalness);
@@ -2775,7 +2789,7 @@
         setEnableFreeScroll(false);
         setEnableDrawingLiveTile(false);
         setRunningTaskHidden(true);
-        setTaskIconScaledDown(true);
+        setTaskIconVisible(false);
     }
 
     /**
@@ -2794,7 +2808,7 @@
      * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
      */
     public void onSwipeUpAnimationSuccess() {
-        animateUpTaskIconScale();
+        startIconFadeInOnGestureComplete();
         setSwipeDownShouldLaunchApp(true);
     }
 
@@ -2921,7 +2935,7 @@
         setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS);
         Log.d(TAG, "onGestureAnimationEnd - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile);
         setRunningTaskHidden(false);
-        animateUpTaskIconScale();
+        startIconFadeInOnGestureComplete();
         animateActionsViewIn();
 
         mCurrentGestureEndTarget = null;
@@ -3045,7 +3059,7 @@
 
         if (mRunningTaskViewId != -1) {
             // Reset the state on the old running task view
-            setTaskIconScaledDown(false);
+            setTaskIconVisible(true);
             setRunningTaskViewShowScreenshot(true);
             setRunningTaskHidden(false);
         }
@@ -3114,25 +3128,31 @@
         }
     }
 
-    public void setTaskIconScaledDown(boolean isScaledDown) {
-        if (mTaskIconScaledDown != isScaledDown) {
-            mTaskIconScaledDown = isScaledDown;
+    /**
+     * Updates icon visibility when going in or out of overview.
+     */
+    public void setTaskIconVisible(boolean isVisible) {
+        if (mTaskIconVisible != isVisible) {
+            mTaskIconVisible = isVisible;
             for (TaskView taskView : getTaskViews()) {
-                taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
+                taskView.setIconVisibleForGesture(mTaskIconVisible);
             }
         }
     }
 
     private void animateActionsViewIn() {
         if (!showAsGrid() || isFocusedTaskInExpectedScrollPosition()) {
-            animateActionsViewAlpha(1, TaskView.SCALE_ICON_DURATION);
+            animateActionsViewAlpha(1, TaskView.FADE_IN_ICON_DURATION);
         }
     }
 
-    public void animateUpTaskIconScale() {
-        mTaskIconScaledDown = false;
+    /**
+     * Updates icon visibility when gesture is settled.
+     */
+    public void startIconFadeInOnGestureComplete() {
+        mTaskIconVisible = true;
         for (TaskView taskView : getTaskViews()) {
-            taskView.animateIconScaleAndDimIntoView();
+            taskView.startIconFadeInOnGestureComplete();
         }
     }
 
@@ -3922,7 +3942,7 @@
                     anim.setFloat(taskView, taskView.getSecondaryDismissTranslationProperty(),
                             secondaryTranslation, clampToProgress(LINEAR, animationStartProgress,
                                     dismissTranslationInterpolationEnd));
-                    anim.add(taskView.getFocusTransitionScaleAndDimOutAnimator(),
+                    anim.add(taskView.getDismissIconFadeOutAnimator(),
                             clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT));
                 } else if ((isFocusedTaskDismissed && nextFocusedTaskView != null && isSameGridRow(
                         taskView, nextFocusedTaskView))
@@ -4106,7 +4126,7 @@
                                     ? INVALID_TASK_ID
                                     : finalNextFocusedTaskView.getTaskViewId());
                             mTopRowIdSet.remove(mFocusedTaskViewId);
-                            finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
+                            finalNextFocusedTaskView.getDismissIconFadeInAnimator().start();
                         }
                         updateTaskSize();
                         updateChildTaskOrientations();
@@ -4701,7 +4721,7 @@
     /**
      * Returns the current list of [TaskView] children.
      */
-    private Iterable<TaskView> getTaskViews() {
+    public Iterable<TaskView> getTaskViews() {
         return mUtils.getTaskViews(getTaskViewCount(), this::requireTaskViewAt);
     }
 
@@ -5394,13 +5414,6 @@
         mSplitHiddenTaskViewIndex = -1;
         if (mSplitHiddenTaskView != null) {
             mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
-            // mSplitHiddenTaskView is set when split select animation starts. The TaskView is only
-            // removed when when the animation finishes. So in the case of overview being dismissed
-            // during the animation, we should not call clearAndRecycleTaskView() because it has
-            // not been removed yet.
-            if (mSplitHiddenTaskView.getParent() == null) {
-                clearAndRecycleTaskView(mSplitHiddenTaskView);
-            }
             mSplitHiddenTaskView = null;
         }
     }
@@ -5888,11 +5901,18 @@
     }
 
     /**
+     * Finish recents animation.
+     */
+    public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
+            @Nullable Runnable onFinishComplete) {
+        finishRecentsAnimation(toRecents, shouldPip, false, onFinishComplete);
+    }
+    /**
      * NOTE: Whatever value gets passed through to the toRecents param may need to also be set on
      * {@link #mRecentsAnimationController#setWillFinishToHome}.
      */
     public void finishRecentsAnimation(boolean toRecents, boolean shouldPip,
-            @Nullable Runnable onFinishComplete) {
+            boolean allAppTargetsAreTranslucent, @Nullable Runnable onFinishComplete) {
         Log.d(TAG, "finishRecentsAnimation - mRecentsAnimationController: "
                 + mRecentsAnimationController);
         // TODO(b/197232424#comment#10) Move this back into onRecentsAnimationComplete(). Maybe?
@@ -5906,8 +5926,9 @@
         }
 
         final boolean sendUserLeaveHint = toRecents && shouldPip;
-        if (sendUserLeaveHint) {
+        if (sendUserLeaveHint && !com.android.wm.shell.Flags.enablePip2()) {
             // Notify the SysUI to use fade-in animation when entering PiP from live tile.
+            // Note: PiP2 handles entering differently, so skip if enable_pip2=true.
             final SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(getContext());
             systemUiProxy.setPipAnimationTypeToAlpha();
             systemUiProxy.setShelfHeight(true, mContainer.getDeviceProfile().hotseatBarSizePx);
@@ -5924,7 +5945,7 @@
                         tx, null /* overlay */);
             }
         }
-        mRecentsAnimationController.finish(toRecents, () -> {
+        mRecentsAnimationController.finish(toRecents, allAppTargetsAreTranslucent, () -> {
             if (onFinishComplete != null) {
                 onFinishComplete.run();
             }
@@ -6457,6 +6478,12 @@
         return mSizeStrategy;
     }
 
+
+    /**
+     * Returns the container interface
+     */
+    protected abstract BaseContainerInterface<STATE_TYPE, ?> getContainerInterface(int displayId);
+
     /**
      * Set all the task views to color tint scrim mode, dimming or tinting them all. Allows the
      * tasks to be dimmed while other elements in the recents view are left alone.
@@ -6770,8 +6797,8 @@
         if (mDesktopRecentsTransitionController == null) {
             return;
         }
-        mDesktopRecentsTransitionController.moveToDesktop(taskContainer.getTask().key.id,
-                transitionSource);
+
+        mDesktopRecentsTransitionController.moveToDesktop(taskContainer, transitionSource);
         successCallback.run();
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 082971c..084ea4b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -31,7 +31,6 @@
 import android.util.FloatProperty
 import android.util.Log
 import android.view.Display
-import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.View
 import android.view.View.OnClickListener
@@ -78,7 +77,6 @@
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.TaskViewUtils
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler
-import com.android.quickstep.task.thumbnail.TaskThumbnailView
 import com.android.quickstep.util.ActiveGestureErrorDetector
 import com.android.quickstep.util.ActiveGestureLog
 import com.android.quickstep.util.BorderAnimator
@@ -434,37 +432,54 @@
     // Used to cache thumbnail bounds to avoid recalculating on every hover move.
     private var thumbnailBounds = Rect()
 
-    private var focusTransitionProgress = 1f
+    // Progress variable indicating if the TaskView is in a settled state:
+    // 0 = The TaskView is in a transitioning state e.g. during gesture, in quickswitch carousel,
+    // becoming focus task etc.
+    // 1 = The TaskView is settled and no longer transitioning
+    private var settledProgress = 1f
         set(value) {
             field = value
-            onFocusTransitionProgressUpdated(field)
+            onSettledProgressUpdated(field)
         }
 
-    private val focusTransitionPropertyFactory =
+    private val settledProgressPropertyFactory =
         MultiPropertyFactory(
             this,
-            FOCUS_TRANSITION,
-            FOCUS_TRANSITION_INDEX_COUNT,
+            SETTLED_PROGRESS,
+            SETTLED_PROGRESS_INDEX_COUNT,
             { x: Float, y: Float -> x * y },
             1f,
         )
-    private val focusTransitionFullscreen =
-        focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_FULLSCREEN)
-    private val focusTransitionScaleAndDim =
-        focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_SCALE_AND_DIM)
+    private val settledProgressFullscreen =
+        settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_FULLSCREEN)
+    private val settledProgressGesture =
+        settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_GESTURE)
+    private val settledProgressDismiss =
+        settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_DISMISS)
 
     /**
-     * Returns an animator of [focusTransitionScaleAndDim] that transition out with a built-in
+     * Returns an animator of [settledProgressDismiss] that transition in with a built-in
      * interpolator.
      */
-    fun getFocusTransitionScaleAndDimOutAnimator(): ObjectAnimator =
+    fun getDismissIconFadeInAnimator(): ObjectAnimator =
+        ObjectAnimator.ofFloat(settledProgressDismiss, MULTI_PROPERTY_VALUE, 1f).apply {
+            duration = FADE_IN_ICON_DURATION
+            interpolator = FADE_IN_ICON_INTERPOLATOR
+        }
+
+    /**
+     * Returns an animator of [settledProgressDismiss] that transition out with a built-in
+     * interpolator. [AnimatedFloat] is used to apply another level of interpolation, on top of
+     * interpolator set to the [Animator] by the caller.
+     */
+    fun getDismissIconFadeOutAnimator(): ObjectAnimator =
         AnimatedFloat { v ->
-                focusTransitionScaleAndDim.value =
-                    FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(v)
+                settledProgressDismiss.value =
+                    SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v)
             }
             .animateToValue(1f, 0f)
 
-    private var iconAndDimAnimator: ObjectAnimator? = null
+    private var iconFadeInOnGestureCompleteAnimator: ObjectAnimator? = null
     // The current background requests to load the task thumbnail and icon
     private val pendingThumbnailLoadRequests = mutableListOf<CancellableTask<*>>()
     private val pendingIconLoadRequests = mutableListOf<CancellableTask<*>>()
@@ -677,6 +692,28 @@
         return super.performAccessibilityAction(action, arguments)
     }
 
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        inflateViewStubs()
+    }
+
+    protected open fun inflateViewStubs() {
+        findViewById<ViewStub>(R.id.snapshot)
+            ?.apply {
+                layoutResource =
+                    if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail
+                    else R.layout.task_thumbnail_deprecated
+            }
+            ?.inflate()
+        findViewById<ViewStub>(R.id.icon)
+            ?.apply {
+                layoutResource =
+                    if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+                    else R.layout.icon_view
+            }
+            ?.inflate()
+    }
+
     /** Updates this task view to the given {@param task}. */
     open fun bind(
         task: Task,
@@ -718,27 +755,11 @@
         @StagePosition stagePosition: Int,
         taskOverlayFactory: TaskOverlayFactory,
     ): TaskContainer {
-        val existingThumbnailView: View = findViewById(thumbnailViewId)!!
-        val snapshotView =
-            when {
-                !enableRefactorTaskThumbnail() -> existingThumbnailView
-                existingThumbnailView is TaskThumbnailView -> existingThumbnailView
-                else -> {
-                    val indexOfSnapshotView = indexOfChild(existingThumbnailView)
-                    LayoutInflater.from(context)
-                        .inflate(R.layout.task_thumbnail, this, false)
-                        .also {
-                            it.id = thumbnailViewId
-                            addView(it, indexOfSnapshotView, existingThumbnailView.layoutParams)
-                            removeView(existingThumbnailView)
-                        }
-                }
-            }
-        val iconView = getOrInflateIconView(iconViewId)
+        val iconView = findViewById<View>(iconViewId) as TaskViewIcon
         return TaskContainer(
             this,
             task,
-            snapshotView,
+            findViewById(thumbnailViewId),
             iconView,
             TransformingTouchDelegate(iconView.asView()),
             stagePosition,
@@ -748,18 +769,6 @@
         )
     }
 
-    protected fun getOrInflateIconView(@IdRes iconViewId: Int): TaskViewIcon {
-        val iconView = findViewById<View>(iconViewId)!!
-        return iconView as? TaskViewIcon
-            ?: (iconView as ViewStub)
-                .apply {
-                    layoutResource =
-                        if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
-                        else R.layout.icon_view
-                }
-                .inflate() as TaskViewIcon
-    }
-
     fun containsMultipleTasks() = taskContainers.size > 1
 
     /**
@@ -1423,23 +1432,23 @@
      * Called to animate a smooth transition when going directly from an app into Overview (and vice
      * versa). Icons fade in, and DWB banners slide in with a "shift up" animation.
      */
-    private fun onFocusTransitionProgressUpdated(focusTransitionProgress: Float) {
+    private fun onSettledProgressUpdated(settledProgress: Float) {
         taskContainers.forEach {
-            it.iconView.setContentAlpha(focusTransitionProgress)
-            it.digitalWellBeingToast?.bannerOffsetPercentage = 1f - focusTransitionProgress
+            it.iconView.setContentAlpha(settledProgress)
+            it.digitalWellBeingToast?.bannerOffsetPercentage = 1f - settledProgress
         }
     }
 
-    fun animateIconScaleAndDimIntoView() {
-        iconAndDimAnimator?.cancel()
-        iconAndDimAnimator =
-            ObjectAnimator.ofFloat(focusTransitionScaleAndDim, MULTI_PROPERTY_VALUE, 0f, 1f).apply {
-                duration = SCALE_ICON_DURATION
+    fun startIconFadeInOnGestureComplete() {
+        iconFadeInOnGestureCompleteAnimator?.cancel()
+        iconFadeInOnGestureCompleteAnimator =
+            ObjectAnimator.ofFloat(settledProgressGesture, MULTI_PROPERTY_VALUE, 1f).apply {
+                duration = FADE_IN_ICON_DURATION
                 interpolator = Interpolators.LINEAR
                 addListener(
                     object : AnimatorListenerAdapter() {
                         override fun onAnimationEnd(animation: Animator) {
-                            iconAndDimAnimator = null
+                            iconFadeInOnGestureCompleteAnimator = null
                         }
                     }
                 )
@@ -1447,9 +1456,9 @@
             }
     }
 
-    fun setIconScaleAndDim(iconScale: Float) {
-        iconAndDimAnimator?.cancel()
-        focusTransitionScaleAndDim.value = iconScale
+    fun setIconVisibleForGesture(isVisible: Boolean) {
+        iconFadeInOnGestureCompleteAnimator?.cancel()
+        settledProgressGesture.value = if (isVisible) 1f else 0f
     }
 
     /** Set a color tint on the snapshot and supporting views. */
@@ -1544,8 +1553,8 @@
             it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
             it.overlay.setFullscreenProgress(fullscreenProgress)
         }
-        focusTransitionFullscreen.value =
-            FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
+        settledProgressFullscreen.value =
+            SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
         updateFullscreenParams()
     }
 
@@ -1602,7 +1611,8 @@
         }
         dismissScale = 1f
         translationZ = 0f
-        setIconScaleAndDim(1f)
+        setIconVisibleForGesture(true)
+        settledProgressDismiss.value = 1f
         setColorTint(0f, 0)
     }
 
@@ -1630,9 +1640,10 @@
         const val FLAG_UPDATE_ALL =
             (FLAG_UPDATE_ICON or FLAG_UPDATE_THUMBNAIL or FLAG_UPDATE_CORNER_RADIUS)
 
-        const val FOCUS_TRANSITION_INDEX_FULLSCREEN = 0
-        const val FOCUS_TRANSITION_INDEX_SCALE_AND_DIM = 1
-        const val FOCUS_TRANSITION_INDEX_COUNT = 2
+        const val SETTLED_PROGRESS_INDEX_FULLSCREEN = 0
+        const val SETTLED_PROGRESS_INDEX_GESTURE = 1
+        const val SETTLED_PROGRESS_INDEX_DISMISS = 2
+        const val SETTLED_PROGRESS_INDEX_COUNT = 3
 
         private const val ALPHA_INDEX_STABLE = 0
         private const val ALPHA_INDEX_ATTACH = 1
@@ -1642,25 +1653,26 @@
 
         /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
         const val MAX_PAGE_SCRIM_ALPHA = 0.4f
-        const val SCALE_ICON_DURATION: Long = 120
+        const val FADE_IN_ICON_DURATION: Long = 120
         private const val DIM_ANIM_DURATION: Long = 700
-        private const val FOCUS_TRANSITION_THRESHOLD =
-            SCALE_ICON_DURATION.toFloat() / DIM_ANIM_DURATION
-        val FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR =
+        private const val SETTLE_TRANSITION_THRESHOLD =
+            FADE_IN_ICON_DURATION.toFloat() / DIM_ANIM_DURATION
+        val SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR =
             Interpolators.clampToProgress(
                 Interpolators.FAST_OUT_SLOW_IN,
-                1f - FOCUS_TRANSITION_THRESHOLD,
+                1f - SETTLE_TRANSITION_THRESHOLD,
                 1f,
             )!!
+        private val FADE_IN_ICON_INTERPOLATOR = Interpolators.LINEAR
         private val SYSTEM_GESTURE_EXCLUSION_RECT = listOf(Rect())
 
-        private val FOCUS_TRANSITION: FloatProperty<TaskView> =
-            object : FloatProperty<TaskView>("focusTransition") {
+        private val SETTLED_PROGRESS: FloatProperty<TaskView> =
+            object : FloatProperty<TaskView>("settleTransition") {
                 override fun setValue(taskView: TaskView, v: Float) {
-                    taskView.focusTransitionProgress = v
+                    taskView.settledProgress = v
                 }
 
-                override fun get(taskView: TaskView) = taskView.focusTransitionProgress
+                override fun get(taskView: TaskView) = taskView.settledProgress
             }
 
         private val SPLIT_SELECT_TRANSLATION_X: FloatProperty<TaskView> =
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 5867e77..eb13b55 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -16,12 +16,15 @@
 
 package com.android.launcher3.taskbar
 
+import android.animation.AnimatorTestRule
 import android.content.ComponentName
 import android.content.Intent
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
+import com.android.launcher3.R
 import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
 import com.android.launcher3.taskbar.rules.MockedRecentsModelTestRule
@@ -75,7 +78,9 @@
 
     @get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context)
 
-    @get:Rule(order = 4) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+    @get:Rule(order = 4) val animatorTestRule = AnimatorTestRule(this)
+
+    @get:Rule(order = 5) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
 
     @InjectController lateinit var taskbarViewController: TaskbarViewController
     @InjectController lateinit var recentAppsController: TaskbarRecentAppsController
@@ -134,6 +139,54 @@
 
     @Test
     @TaskbarMode(PINNED)
+    fun testOverflownTaskbarWithNoSpaceForRecentApps_pinned() {
+        val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+
+        // Create two "recent" desktop tasks, and then add enough hotseat items so the taskbar
+        // reaches max number of items with hotseat item icons, all apps and divider icons only.
+        // I.e. so all desktop tasks are in taskbar overflow.
+        createDesktopTask(2)
+        runOnMainSync {
+            val taskbarView: TaskbarView =
+                taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+            taskbarView.updateItems(
+                createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount),
+                recentAppsController.shownTasks,
+            )
+        }
+
+        // Verify that taskbar overflow view is shown (eventhough it exceeds max taskbar icons).
+        assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumberOfTaskbarIcons + 1)
+        assertThat(taskbarOverflowIconIndex).isEqualTo(maxNumberOfTaskbarIcons)
+        assertThat(overflowItems).containsExactlyElementsIn(0..1)
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
+    fun testOverflownTaskbarWithNoSpaceForRecentApps_singleRecent_pinned() {
+        val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+
+        // Create a "recent" desktop task, and then add enough hotseat items so the taskbar
+        // reaches max number of items with hotseat item icons, all apps and divider icons only.
+        // I.e. so the single desktop tasks is in taskbar overflow.
+        createDesktopTask(1)
+        runOnMainSync {
+            val taskbarView: TaskbarView =
+                taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+            taskbarView.updateItems(
+                createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount),
+                recentAppsController.shownTasks,
+            )
+        }
+
+        // Verify that recent task is shown (eventhough it exceeds max taskbar icons), and that
+        // the taskbar overflow view is not added for the single recent app.
+        assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumberOfTaskbarIcons + 1)
+        assertThat(taskbarOverflowIconIndex).isEqualTo(-1)
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
     fun testBubbleBarReducesTaskbarMaxNumIcons_pinned() {
         var initialMaxNumIconViews = maxNumberOfTaskbarIcons
         assertThat(initialMaxNumIconViews).isGreaterThan(0)
@@ -208,7 +261,10 @@
         val initialNumIcons = currentNumberOfTaskbarIcons
         val initialMaxNumIconViews = addRunningAppsAndVerifyOverflowState(5)
 
-        runOnMainSync { bubbleBarViewController.setHiddenForBubbles(true) }
+        runOnMainSync {
+            bubbleBarViewController.setHiddenForBubbles(true)
+            animatorTestRule.advanceTimeBy(150)
+        }
 
         val maxNumIconViews = maxNumberOfTaskbarIcons
         assertThat(maxNumIconViews).isGreaterThan(initialMaxNumIconViews)
@@ -226,7 +282,10 @@
         val initialNumIcons = currentNumberOfTaskbarIcons
         val initialMaxNumIconViews = addRunningAppsAndVerifyOverflowState(5)
 
-        runOnMainSync { bubbleBarViewController.setHiddenForBubbles(true) }
+        runOnMainSync {
+            bubbleBarViewController.setHiddenForBubbles(true)
+            animatorTestRule.advanceTimeBy(150)
+        }
 
         val maxNumIconViews = maxNumberOfTaskbarIcons
         assertThat(maxNumIconViews).isGreaterThan(initialMaxNumIconViews)
@@ -287,6 +346,20 @@
             }
         }
 
+    private val overflowItems: List<Int>
+        get() {
+            return getOnUiThread {
+                val overflowIcon =
+                    taskbarViewController.iconViews.firstOrNull { it is TaskbarOverflowView }
+
+                if (overflowIcon is TaskbarOverflowView) {
+                    overflowIcon.itemIds
+                } else {
+                    emptyList()
+                }
+            }
+        }
+
     /**
      * Adds enough running apps for taskbar to enter overflow of `targetOverflowSize`, and verifies
      * * max number of icons in the taskbar remains unchanged
@@ -309,6 +382,9 @@
         assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
         assertThat(taskbarOverflowIconIndex)
             .isEqualTo(if (targetOverflowSize > 0) initialIconCount else -1)
+        if (targetOverflowSize > 0) {
+            assertThat(overflowItems).containsExactlyElementsIn(0..targetOverflowSize)
+        }
         return maxNumIconViews
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt
index 0bb404b..44d31c4 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt
@@ -154,4 +154,13 @@
         }
         assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, RECENT)
     }
+
+    @Test
+    fun testUpdateItem_addHotseatItemAfterRecentsItem_hotseatItemBeforeDivider() {
+        runOnMainSync {
+            taskbarView.updateItems(emptyArray(), createRecents(1))
+            taskbarView.updateItems(createHotseatItems(1), createRecents(1))
+        }
+        assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, RECENT)
+    }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
index a6bdbb0..f2dcf77 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
@@ -99,7 +99,7 @@
     /** Verifies that recents from [startIndex] have IDs that match [expectedIds] in order. */
     fun hasRecentsOrder(startIndex: Int, expectedIds: List<Int>) {
         val actualIds =
-            view.iconViews.slice(startIndex..<expectedIds.size).map {
+            view.iconViews.slice(startIndex..<startIndex + expectedIds.size).map {
                 assertThat(it.tag).isInstanceOf(GroupTask::class.java)
                 (it.tag as? GroupTask)?.task1?.key?.id
             }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
index 78d8e5d..4b6d5e5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
@@ -18,6 +18,7 @@
 
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
+import android.view.View
 import com.android.launcher3.Flags.FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION
 import com.android.launcher3.R
 import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
@@ -33,6 +34,7 @@
 import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
 import com.android.launcher3.util.LauncherMultivalentJUnit
 import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -49,6 +51,9 @@
 
     private lateinit var taskbarView: TaskbarView
 
+    private val iconViews: Array<View>
+        get() = taskbarView.iconViews
+
     @Before
     fun obtainView() {
         taskbarView = taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
@@ -131,4 +136,93 @@
         }
         assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, HOTSEAT, ALL_APPS)
     }
+
+    @Test
+    fun testUpdateItems_addRecentsItem_viewAddedOnRight() {
+        runOnMainSync {
+            taskbarView.updateItems(emptyArray(), createRecents(1))
+            val prevIconViews = iconViews
+
+            val newRecents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), newRecents)
+
+            assertThat(taskbarView).hasRecentsOrder(startIndex = 2, expectedIds = listOf(0, 1))
+            assertThat(iconViews[2]).isSameInstanceAs(prevIconViews[2])
+            assertThat(iconViews.last() in prevIconViews).isFalse()
+        }
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtl_addRecentsItem_viewAddedOnLeft() {
+        runOnMainSync {
+            taskbarView.updateItems(emptyArray(), createRecents(1))
+            val prevIconViews = iconViews
+
+            val newRecents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), newRecents)
+
+            assertThat(taskbarView).hasRecentsOrder(startIndex = 0, expectedIds = listOf(1, 0))
+            assertThat(iconViews[1]).isSameInstanceAs(prevIconViews.first())
+            assertThat(iconViews.first() in prevIconViews).isFalse()
+        }
+    }
+
+    @Test
+    fun testUpdateItems_removeFirstRecentsItem_correspondingViewRemoved() {
+        runOnMainSync {
+            val recents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), recents)
+
+            val expectedViewToRemove = iconViews[2]
+            assertThat(expectedViewToRemove.tag).isEqualTo(recents.first())
+
+            taskbarView.updateItems(emptyArray(), listOf(recents.last()))
+            assertThat(expectedViewToRemove in iconViews).isFalse()
+        }
+    }
+
+    @Test
+    fun testUpdateItems_removeLastRecentsItem_correspondingViewRemoved() {
+        runOnMainSync {
+            val recents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), recents)
+
+            val expectedViewToRemove = iconViews[3]
+            assertThat(expectedViewToRemove.tag).isEqualTo(recents.last())
+
+            taskbarView.updateItems(emptyArray(), listOf(recents.first()))
+            assertThat(expectedViewToRemove in iconViews).isFalse()
+        }
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtl_removeFirstRecentsItem_correspondingViewRemoved() {
+        runOnMainSync {
+            val recents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), recents)
+
+            val expectedViewToRemove = iconViews[1]
+            assertThat(expectedViewToRemove.tag).isEqualTo(recents.first())
+
+            taskbarView.updateItems(emptyArray(), listOf(recents.last()))
+            assertThat(expectedViewToRemove in iconViews).isFalse()
+        }
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtl_removeLastRecentsItem_correspondingViewRemoved() {
+        runOnMainSync {
+            val recents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), recents)
+
+            val expectedViewToRemove = iconViews[0]
+            assertThat(expectedViewToRemove.tag).isEqualTo(recents.last())
+
+            taskbarView.updateItems(emptyArray(), listOf(recents.first()))
+            assertThat(expectedViewToRemove in iconViews).isFalse()
+        }
+    }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt
index d5a76a2..eae181f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt
@@ -40,7 +40,7 @@
                 iconSize = 40f,
                 expandedBarIconSpacing = 10f,
                 bubbleCount = 5,
-                onLeft = false
+                onLeft = false,
             )
         val listener = TestBubbleAnimatorListener()
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -61,7 +61,7 @@
                 iconSize = 40f,
                 expandedBarIconSpacing = 10f,
                 bubbleCount = 5,
-                onLeft = false
+                onLeft = false,
             )
         val listener = TestBubbleAnimatorListener()
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
@@ -69,7 +69,8 @@
                 bubbleIndex = 2,
                 selectedBubbleIndex = 3,
                 removingLastBubble = false,
-                listener
+                removingLastRemainingBubble = false,
+                listener,
             )
         }
 
@@ -87,14 +88,14 @@
                 iconSize = 40f,
                 expandedBarIconSpacing = 10f,
                 bubbleCount = 5,
-                onLeft = false
+                onLeft = false,
             )
         val listener = TestBubbleAnimatorListener()
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
             bubbleAnimator.animateNewAndRemoveOld(
                 selectedBubbleIndex = 3,
                 removedBubbleIndex = 2,
-                listener
+                listener,
             )
         }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index 29d142f..44070cf 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -23,6 +23,7 @@
 import android.graphics.drawable.ColorDrawable
 import android.view.LayoutInflater
 import android.view.View
+import android.view.View.INVISIBLE
 import android.view.View.VISIBLE
 import android.widget.FrameLayout
 import android.widget.TextView
@@ -78,7 +79,7 @@
     private lateinit var flyoutContainer: FrameLayout
     private lateinit var bubbleStashController: BubbleStashController
     private lateinit var flyoutController: BubbleBarFlyoutController
-    private val onExpandedNoOp = Runnable {}
+    private val emptyRunnable = Runnable {}
 
     private val flyoutView: View?
         get() = flyoutContainer.findViewById(R.id.bubble_bar_flyout_view)
@@ -106,7 +107,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpandedNoOp,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -162,7 +164,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpandedNoOp,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -197,6 +200,8 @@
         assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2)
         assertThat(animatorScheduler.delayedBlock).isNull()
         assertThat(bubbleBarView.alpha).isEqualTo(1)
+        assertThat(bubbleBarView.scaleX).isEqualTo(1)
+        assertThat(bubbleBarView.scaleY).isEqualTo(1)
         assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
         assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
         assertThat(animator.isAnimating).isFalse()
@@ -217,7 +222,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpandedNoOp,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -241,6 +247,8 @@
         // verify that the hide animation was canceled
         assertThat(animatorScheduler.delayedBlock).isNull()
         assertThat(animator.isAnimating).isFalse()
+        assertThat(bubbleBarView.scaleX).isEqualTo(1)
+        assertThat(bubbleBarView.scaleY).isEqualTo(1)
         verify(bubbleStashController).onNewBubbleAnimationInterrupted(any(), any())
 
         // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait
@@ -264,7 +272,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpandedNoOp,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -316,7 +325,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpandedNoOp,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -355,7 +365,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpanded,
+                onExpanded = onExpanded,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -401,7 +412,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpanded,
+                onExpanded = onExpanded,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -453,7 +465,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpanded,
+                onExpanded = onExpanded,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -504,17 +517,21 @@
 
         val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
 
+        var notifiedBubbleBarVisible = false
+        val onBubbleBarVisible = Runnable { notifiedBubbleBarVisible = true }
         val animator =
             BubbleBarViewAnimator(
                 bubbleBarView,
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpandedNoOp,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = onBubbleBarVisible,
                 animatorScheduler,
             )
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubbleBarView.visibility = INVISIBLE
             animator.animateToInitialState(bubble, isInApp = true, isExpanding = false)
         }
 
@@ -542,6 +559,8 @@
         assertThat(bubbleBarView.alpha).isEqualTo(0)
         assertThat(handle.translationY).isEqualTo(0)
         assertThat(handle.alpha).isEqualTo(1)
+        assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
+        assertThat(notifiedBubbleBarVisible).isTrue()
 
         verify(bubbleStashController).stashBubbleBarImmediate()
     }
@@ -567,7 +586,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpanded,
+                onExpanded = onExpanded,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -603,7 +623,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpandedNoOp,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -649,7 +670,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpanded,
+                onExpanded = onExpanded,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -699,7 +721,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpanded,
+                onExpanded = onExpanded,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -747,7 +770,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpandedNoOp,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -803,7 +827,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpanded,
+                onExpanded = onExpanded,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -858,7 +883,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpanded,
+                onExpanded = onExpanded,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -923,7 +949,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpanded,
+                onExpanded = onExpanded,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -983,7 +1010,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpandedNoOp,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -1053,7 +1081,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpandedNoOp,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -1129,7 +1158,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpandedNoOp,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -1216,7 +1246,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpandedNoOp,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
@@ -1330,7 +1361,8 @@
                 bubbleStashController,
                 flyoutController,
                 bubbleBarParentViewController,
-                onExpandedNoOp,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = emptyRunnable,
                 animatorScheduler,
             )
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
index 970bdec..6dce10b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
@@ -30,7 +30,9 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.animation.ValueAnimator;
@@ -58,14 +60,18 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulContainer;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.SystemUiController;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
+import com.android.quickstep.fallback.window.RecentsWindowFactory;
 import com.android.quickstep.util.ContextInitListener;
+import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.RecentsViewContainer;
 import com.android.systemui.shared.Flags;
 import com.android.systemui.shared.system.InputConsumerController;
 
+import com.google.android.msdl.data.model.MSDLToken;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -123,6 +129,7 @@
     @Mock protected LauncherRootView mRootView;
     @Mock protected SystemUiController mSystemUiController;
     @Mock protected GestureState mGestureState;
+    @Mock protected MSDLPlayerWrapper mMSDLPlayerWrapper;
 
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -173,7 +180,8 @@
 
     @Before
     public void setUpRecentsContainer() {
-        mTaskAnimationManager = new TaskAnimationManager(mContext, getRecentsWindowManager());
+        mTaskAnimationManager = new TaskAnimationManager(mContext, getRecentsWindowFactory(),
+                mRecentsAnimationDeviceState);
         RecentsViewContainer recentsContainer = getRecentsContainer();
         RECENTS_VIEW recentsView = getRecentsView();
 
@@ -304,6 +312,17 @@
         });
     }
 
+    @Test
+    @EnableFlags(com.android.launcher3.Flags.FLAG_MSDL_FEEDBACK)
+    public void onMotionPauseDetected_playsSwipeThresholdToken() {
+        SWIPE_HANDLER handler = createSwipeHandler();
+        MotionPauseDetector.OnMotionPauseListener listener = handler.getMotionPauseListener();
+        listener.onMotionPauseDetected();
+
+        verify(mMSDLPlayerWrapper, times(1)).playToken(eq(MSDLToken.SWIPE_THRESHOLD_INDICATOR));
+        verifyNoMoreInteractions(mMSDLPlayerWrapper);
+    }
+
     /**
      * Verifies that RecentsAnimationController#finish() is called, and captures and runs any
      * callback that was passed to it. This ensures that STATE_CURRENT_TASK_FINISHED is correctly
@@ -365,7 +384,7 @@
     }
 
     @Nullable
-    protected RecentsWindowManager getRecentsWindowManager() {
+    protected RecentsWindowFactory getRecentsWindowFactory() {
         return null;
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
index 88197e5..d4eb8e2 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
@@ -49,7 +49,8 @@
                 mGestureState,
                 touchTimeMs,
                 continuingLastGesture,
-                mInputConsumerController);
+                mInputConsumerController,
+                mMSDLPlayerWrapper);
     }
 
     @NonNull
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
index 32b5b85..37accdb 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
@@ -19,11 +19,15 @@
 import android.graphics.PointF
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.Flags.enableLauncherOverviewInWindow
 import com.android.launcher3.R
 import com.android.launcher3.dagger.LauncherAppComponent
 import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.MSDLPlayerWrapper
 import com.android.quickstep.dagger.QuickStepModule
+import com.android.quickstep.fallback.window.RecentsWindowFactory
 import com.android.systemui.contextualeducation.GestureType
 import com.android.systemui.shared.system.InputConsumerController
 import dagger.BindsInstance
@@ -51,6 +55,8 @@
 
     @Mock private lateinit var systemUiProxy: SystemUiProxy
 
+    @Mock private lateinit var msdlPlayerWrapper: MSDLPlayerWrapper
+
     private lateinit var underTest: LauncherSwipeHandlerV2
 
     @get:Rule val mockitoRule = MockitoJUnit.rule()
@@ -68,6 +74,16 @@
         )
         val deviceState = mock(RecentsAnimationDeviceState::class.java)
         whenever(deviceState.rotationTouchHelper).thenReturn(mock(RotationTouchHelper::class.java))
+
+        if (enableLauncherOverviewInWindow()) {
+            // Initialize an instance of RecentsWindowFactory directly to simulate its
+            // initialization in TouchInteractionService#onCreate.
+            // This instance will then be used in OverviewComponentObserver.
+            InstrumentationRegistry.getInstrumentation().runOnMainSync {
+                RecentsWindowFactory(sandboxContext)
+            }
+        }
+
         gestureState = spy(GestureState(OverviewComponentObserver(sandboxContext, deviceState), 0))
 
         underTest =
@@ -79,6 +95,7 @@
                 0,
                 false,
                 inputConsumerController,
+                msdlPlayerWrapper,
             )
         underTest.onGestureStarted(/* isLikelyToStartNewTask= */ false)
     }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
index ec1dc8b..fc6acfd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
@@ -78,7 +78,8 @@
                 mGestureState,
                 touchTimeMs,
                 continuingLastGesture,
-                mInputConsumerController);
+                mInputConsumerController,
+                mMSDLPlayerWrapper);
     }
 
     @NonNull
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
index 1bdf273..3287fb5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
@@ -16,6 +16,9 @@
 
 package com.android.quickstep;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
@@ -23,10 +26,12 @@
 import com.android.launcher3.util.LauncherMultivalentJUnit;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.fallback.window.RecentsWindowFactory;
 import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
 import com.android.quickstep.views.RecentsViewContainer;
 
+import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
@@ -39,8 +44,14 @@
         RecentsWindowSwipeHandler,
         FallbackWindowInterface> {
 
-    @Mock private RecentsWindowManager mRecentsWindowManager;
+    @Mock private RecentsWindowFactory mRecentsWindowFactory;
     @Mock private FallbackRecentsView<RecentsWindowManager> mRecentsView;
+    @Mock private RecentsWindowManager mRecentsWindowManager;
+
+    @Before
+    public void setupRecentsWindowFactory() {
+        when(mRecentsWindowFactory.get(anyInt())).thenReturn(mRecentsWindowManager);
+    }
 
     @NonNull
     @Override
@@ -54,13 +65,14 @@
                 touchTimeMs,
                 continuingLastGesture,
                 mInputConsumerController,
-                mRecentsWindowManager);
+                mRecentsWindowFactory,
+                mMSDLPlayerWrapper);
     }
 
     @Nullable
     @Override
-    protected RecentsWindowManager getRecentsWindowManager() {
-        return mRecentsWindowManager;
+    protected RecentsWindowFactory getRecentsWindowFactory() {
+        return mRecentsWindowFactory;
     }
 
     @NonNull
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
index d6688d6..1c9ce0b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
@@ -49,6 +49,9 @@
     override fun getThumbnailById(taskId: Int): Flow<ThumbnailData?> =
         getTaskDataById(taskId).map { it?.thumbnail }
 
+    override fun getCurrentThumbnailById(taskId: Int): ThumbnailData? =
+        tasks.value.firstOrNull { it.key.id == taskId }?.thumbnail
+
     override fun setVisibleTasks(visibleTaskIdList: Set<Int>) {
         visibleTasks.value = visibleTaskIdList
         tasks.value =
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index 357df6e..e0e7f28 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -87,6 +87,48 @@
         }
 
     @Test
+    fun getThumbnailByIdReturnsNullWithNoLoadedThumbnails() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+
+            assertThat(systemUnderTest.getThumbnailById(1).first()).isNull()
+        }
+
+    @Test
+    fun getCurrentThumbnailByIdReturnsNullWithNoLoadedThumbnails() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+
+            assertThat(systemUnderTest.getCurrentThumbnailById(1)).isNull()
+        }
+
+    @Test
+    fun getThumbnailByIdReturnsThumbnailWithLoadedThumbnails() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+            val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
+
+            systemUnderTest.setVisibleTasks(setOf(1))
+
+            assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1)
+        }
+
+    @Test
+    fun getCurrentThumbnailByIdReturnsThumbnailWithLoadedThumbnails() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+            val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
+
+            systemUnderTest.setVisibleTasks(setOf(1))
+
+            assertThat(systemUnderTest.getCurrentThumbnailById(1)?.thumbnail).isEqualTo(bitmap1)
+        }
+
+    @Test
     fun setVisibleTasksPopulatesThumbnails() =
         testScope.runTest {
             recentsModel.seedTasks(defaultTaskList)
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index c53c177..1c4ce74 100644
--- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -42,13 +42,13 @@
 import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
+import android.os.Process;
 import android.os.UserHandle;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.TextUtils;
 
-import androidx.test.core.content.pm.ApplicationInfoBuilder;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
@@ -122,10 +122,10 @@
         mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class);
         doAnswer(i -> {
             String pkg = i.getArgument(0);
-            ApplicationInfo applicationInfo = ApplicationInfoBuilder.newBuilder()
-                    .setPackageName(pkg)
-                    .setName("App " + pkg)
-                    .build();
+            ApplicationInfo applicationInfo = new ApplicationInfo();
+            applicationInfo.packageName = pkg;
+            applicationInfo.name = "App " + pkg;
+            applicationInfo.uid = Process.myUid();
             applicationInfo.category = CATEGORY_PRODUCTIVITY;
             applicationInfo.flags = FLAG_INSTALLED;
             return applicationInfo;
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index 066ddc0..ed0c928 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -847,12 +847,42 @@
         verify(taskbarViewController, times(2)).commitRunningAppsToUI()
     }
 
+    @Test
+    fun onRecentTasksChanged_inDesktopMode_sameHotseatPackage_differentUser_isInShownTasks() {
+        setInDesktopMode(true)
+        val hotseatPackageUser = PackageUser(HOTSEAT_PACKAGE_1, USER_HANDLE_2)
+        val hotseatPackageUsers = listOf(hotseatPackageUser)
+        val runningTask = createTask(id = 1, HOTSEAT_PACKAGE_1, localUserHandle = USER_HANDLE_1)
+        val runningTasks = listOf(runningTask)
+        prepareHotseatAndRunningAndRecentAppsInternal(
+            hotseatPackageUsers = hotseatPackageUsers,
+            runningTasks = runningTasks,
+            recentTaskPackages = emptyList(),
+        )
+        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+        assertThat(shownTasks).contains(runningTask)
+        assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1))
+    }
+
     private fun prepareHotseatAndRunningAndRecentApps(
         hotseatPackages: List<String>,
         runningTasks: List<Task>,
         recentTaskPackages: List<String>,
     ): Array<ItemInfo?> {
-        val hotseatItems = createHotseatItemsFromPackageNames(hotseatPackages)
+        val hotseatPackageUsers = hotseatPackages.map { PackageUser(it, myUserHandle) }
+        return prepareHotseatAndRunningAndRecentAppsInternal(
+            hotseatPackageUsers,
+            runningTasks,
+            recentTaskPackages,
+        )
+    }
+
+    private fun prepareHotseatAndRunningAndRecentAppsInternal(
+        hotseatPackageUsers: List<PackageUser>,
+        runningTasks: List<Task>,
+        recentTaskPackages: List<String>,
+    ): Array<ItemInfo?> {
+        val hotseatItems = createHotseatItemsFromPackageUsers(hotseatPackageUsers)
         recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
         updateRecentTasks(runningTasks, recentTaskPackages)
         return recentAppsController.shownHotseatItems.toTypedArray()
@@ -877,12 +907,14 @@
         recentTasksChangedListener?.onRecentTasksChanged()
     }
 
-    private fun createHotseatItemsFromPackageNames(packageNames: List<String>): List<ItemInfo> {
-        return packageNames
+    private fun createHotseatItemsFromPackageUsers(
+        packageUsers: List<PackageUser>
+    ): List<ItemInfo> {
+        return packageUsers
             .map {
-                createTestAppInfo(packageName = it).apply {
+                createTestAppInfo(packageName = it.packageName, userHandle = it.userHandle).apply {
                     container =
-                        if (it.startsWith("predicted")) {
+                        if (it.packageName.startsWith("predicted")) {
                             CONTAINER_HOTSEAT_PREDICTION
                         } else {
                             CONTAINER_HOTSEAT
@@ -895,13 +927,8 @@
     private fun createTestAppInfo(
         packageName: String = "testPackageName",
         className: String = "testClassName",
-    ) =
-        AppInfo(
-            ComponentName(packageName, className),
-            className /* title */,
-            myUserHandle,
-            Intent(),
-        )
+        userHandle: UserHandle,
+    ) = AppInfo(ComponentName(packageName, className), className /* title */, userHandle, Intent())
 
     private fun createRecentTasksFromPackageNames(packageNames: List<String>): List<GroupTask> {
         return packageNames.map { packageName ->
@@ -969,4 +996,6 @@
         const val RECENT_PACKAGE_3 = "recent3"
         const val RECENT_SPLIT_PACKAGES_1 = "split1_split2"
     }
+
+    data class PackageUser(val packageName: String, val userHandle: UserHandle)
 }
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index 6a7b6f8..f57c35f 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -20,18 +20,28 @@
 
 import android.os.SystemProperties;
 
+import androidx.annotation.NonNull;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.tapl.LaunchedAppState;
+import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.Wait;
+import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
 
 import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
 /**
  * Base class for all instrumentation tests that deal with Quickstep.
  */
@@ -54,6 +64,55 @@
         }
     }
 
+    // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call
+    // expecting the results of that gesture because the wait can hide flakeness.
+    protected void waitForRecentsWindowState(String message, Supplier<RecentsState> state) {
+        waitForRecentsWindowCondition(message, recentsWindow ->
+                recentsWindow.getStateManager().getCurrentStableState() == state.get());
+    }
+
+    // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
+    // flakiness.
+    protected void waitForRecentsWindowCondition(String
+            message, Function<RecentsWindowManager, Boolean> condition) {
+        waitForRecentsWindowCondition(message, condition, TestUtil.DEFAULT_UI_TIMEOUT);
+    }
+
+    protected <T> T getFromRecentsWindowManager(Function<RecentsWindowManager, T> f) {
+        if (!TestHelpers.isInLauncherProcess()) return null;
+        return getOnUiThread(
+                () -> f.apply(RecentsWindowManager.getRecentsWindowTracker().getCreatedContext()));
+    }
+
+    protected void executeOnRecentsWindow(Consumer<RecentsWindowManager> f) {
+        getFromRecentsWindowManager(recentsWindow -> {
+            f.accept(recentsWindow);
+            return null;
+        });
+    }
+
+    protected void executeOnRecentsViewContainerInTearDown(
+            @NonNull Consumer<RecentsViewContainer> f) {
+        executeOnRecentsWindow(container -> {
+            if (container != null) f.accept(container);
+        });
+    }
+
+    // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
+    // flakiness.
+    protected void waitForRecentsWindowCondition(
+            String message, Function<RecentsWindowManager, Boolean> condition, long timeout) {
+        verifyKeyguardInvisible();
+        if (!TestHelpers.isInLauncherProcess()) return;
+        Wait.atMost(message, () -> getFromRecentsWindowManager(condition), mLauncher, timeout);
+    }
+
+    protected boolean isInRecentsWindowState(Supplier<RecentsState> state) {
+        if (!TestHelpers.isInLauncherProcess()) return true;
+        return getFromRecentsWindowManager(
+                recentsWindow -> recentsWindow.getStateManager().getState() == state.get());
+    }
+
     protected void assertTestActivityIsRunning(int activityNumber, String message) {
         assertTrue(message, mDevice.wait(
                 Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity" + activityNumber)),
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index aa105f9..695211b 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -19,6 +19,7 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.launcher3.Flags.enableFallbackOverviewInWindow;
 import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
 import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage;
 import static com.android.launcher3.tapl.TestHelpers.getLauncherInMyProcess;
@@ -63,7 +64,9 @@
 import com.android.launcher3.util.rule.TestIsolationRule;
 import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.launcher3.util.rule.ViewCaptureRule;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
 
 import org.junit.After;
 import org.junit.Before;
@@ -191,27 +194,29 @@
     @Test
     public void goToOverviewFromApp() {
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
-        waitForRecentsActivityStop();
+        waitForRecentsClosed();
 
         mLauncher.getLaunchedAppState().switchToOverview();
     }
 
-    protected void executeOnRecents(Consumer<RecentsActivity> f) {
+    protected void executeOnRecents(Consumer<RecentsViewContainer> f) {
         getFromRecents(r -> {
             f.accept(r);
             return true;
         });
     }
 
-    protected <T> T getFromRecents(Function<RecentsActivity, T> f) {
+    protected <T> T getFromRecents(Function<RecentsViewContainer, T> f) {
         if (!TestHelpers.isInLauncherProcess()) return null;
         Object[] result = new Object[1];
         Wait.atMost("Failed to get from recents", () -> MAIN_EXECUTOR.submit(() -> {
-            RecentsActivity activity = RecentsActivity.ACTIVITY_TRACKER.getCreatedContext();
-            if (activity == null) {
+            RecentsViewContainer recentsViewContainer = enableFallbackOverviewInWindow()
+                    ? RecentsWindowManager.getRecentsWindowTracker().getCreatedContext()
+                    : RecentsActivity.ACTIVITY_TRACKER.getCreatedContext();
+            if (recentsViewContainer == null) {
                 return false;
             }
-            result[0] = f.apply(activity);
+            result[0] = f.apply(recentsViewContainer);
             return true;
         }).get(), mLauncher);
         return (T) result[0];
@@ -224,14 +229,19 @@
 
     private void pressHomeAndWaitForOverviewClose() {
         mDevice.pressHome();
-        waitForRecentsActivityStop();
+        waitForRecentsClosed();
     }
 
-    private void waitForRecentsActivityStop() {
+    private void waitForRecentsClosed() {
         try {
-            final boolean recentsActivityIsNull = MAIN_EXECUTOR.submit(
-                    () -> RecentsActivity.ACTIVITY_TRACKER.getCreatedContext() == null).get();
-            if (recentsActivityIsNull) {
+            final boolean isRecentsContainerNUll = MAIN_EXECUTOR.submit(() -> {
+                RecentsViewContainer recentsViewContainer = enableFallbackOverviewInWindow()
+                        ? RecentsWindowManager.getRecentsWindowTracker().getCreatedContext()
+                        : RecentsActivity.ACTIVITY_TRACKER.getCreatedContext();
+
+                return recentsViewContainer == null;
+            }).get();
+            if (isRecentsContainerNUll) {
                 // Null activity counts as a "stopped" one.
                 return;
             }
@@ -241,7 +251,7 @@
             throw new RuntimeException(e);
         }
 
-        Wait.atMost("Recents activity didn't stop",
+        Wait.atMost("Recents view container didn't close",
                 () -> getFromRecents(recents -> !recents.isStarted()),
                 mLauncher);
     }
@@ -251,7 +261,7 @@
         startAppFast(getAppPackageName());
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
         startTestActivity(2);
-        waitForRecentsActivityStop();
+        waitForRecentsClosed();
         Wait.atMost("Expected three apps in the task list",
                 () -> mLauncher.getRecentTasks().size() >= 3,
                 mLauncher);
@@ -312,12 +322,12 @@
         );
     }
 
-    private int getCurrentOverviewPage(RecentsActivity recents) {
-        return recents.<RecentsView>getOverviewPanel().getCurrentPage();
+    private int getCurrentOverviewPage(RecentsViewContainer recentsViewContainer) {
+        return recentsViewContainer.<RecentsView>getOverviewPanel().getCurrentPage();
     }
 
-    private int getTaskCount(RecentsActivity recents) {
-        return recents.<RecentsView>getOverviewPanel().getTaskViewCount();
+    private int getTaskCount(RecentsViewContainer recentsViewContainer) {
+        return recentsViewContainer.<RecentsView>getOverviewPanel().getTaskViewCount();
     }
 
     private class OverviewUpdateHandler {
diff --git a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
index 5dc6932..2c6b750 100644
--- a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
@@ -56,7 +56,7 @@
 import com.android.launcher3.util.LockedUserState;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
+import com.android.quickstep.fallback.window.RecentsWindowFactory;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
 import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
@@ -96,10 +96,9 @@
 
     @NonNull private final MainThreadInitializedObject.SandboxContext mContext =
             new MainThreadInitializedObject.SandboxContext(getApplicationContext());
-    @NonNull private final TaskAnimationManager mTaskAnimationManager = new TaskAnimationManager(
-            mContext, mock(RecentsWindowManager.class));
     @NonNull private final InputMonitorCompat mInputMonitorCompat = new InputMonitorCompat("", 0);
 
+    private TaskAnimationManager mTaskAnimationManager;
     private InputChannelCompat.InputEventReceiver mInputEventReceiver;
     @Nullable private ResetGestureInputConsumer mResetGestureInputConsumer;
     @NonNull private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = (state) -> null;
@@ -121,6 +120,12 @@
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Before
+    public void setupTaskAnimationManager() {
+        mTaskAnimationManager = new TaskAnimationManager(
+                mContext, mock(RecentsWindowFactory.class), mDeviceState);
+    }
+
+    @Before
     public void setupMainThreadInitializedObjects() {
         mContext.putObject(LockedUserState.INSTANCE, mLockedUserState);
     }
@@ -184,6 +189,7 @@
 
     @Before
     public void setupDeviceState() {
+        when(mDeviceState.getDisplayId()).thenReturn(0);
         when(mDeviceState.canStartTrackpadGesture()).thenReturn(true);
         when(mDeviceState.canStartSystemGesture()).thenReturn(true);
         when(mDeviceState.isFullyGesturalNavMode()).thenReturn(true);
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 77f4c05..c713c3d 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -16,7 +16,7 @@
 
 package com.android.quickstep;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.quickstep.NavigationModeSwitchRule.Mode.ALL;
 import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON;
@@ -26,6 +26,7 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Process;
 import android.util.Log;
 
 import androidx.test.uiautomator.UiDevice;
@@ -153,7 +154,9 @@
 
         Log.d(TAG, "setActiveOverlay: " + overlayPackage + "...");
         UiDevice.getInstance(getInstrumentation()).executeShellCommand(
-                "cmd overlay enable-exclusive --category " + overlayPackage);
+                String.format("cmd overlay enable-exclusive --user %d --category %s",
+                        Process.myUserHandle().getIdentifier(),
+                        overlayPackage));
 
         if (currentSysUiNavigationMode() != expectedMode) {
             final CountDownLatch latch = new CountDownLatch(1);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index f1fe2d2..ec6a9c3 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -16,6 +16,7 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.Flags.enableLauncherOverviewInWindow;
 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
@@ -30,13 +31,13 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.Until;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.tapl.BaseOverview;
 import com.android.launcher3.tapl.LaunchedAppState;
@@ -53,7 +54,9 @@
 import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
+import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
 
 import org.junit.After;
 import org.junit.Before;
@@ -61,6 +64,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.function.Consumer;
+
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsQuickstep extends AbstractQuickStepTest {
@@ -70,19 +75,33 @@
     private static final String READ_DEVICE_CONFIG_PERMISSION =
             "android.permission.READ_DEVICE_CONFIG";
 
+    private enum ExpectedState {
+
+        HOME(LauncherState.NORMAL, RecentsState.HOME),
+        OVERVIEW(LauncherState.OVERVIEW, RecentsState.DEFAULT);
+
+        private final LauncherState mLauncherState;
+        private final RecentsState mRecentsState;
+
+        ExpectedState(LauncherState launcherState, RecentsState recentsState) {
+            this.mLauncherState = launcherState;
+            this.mRecentsState = recentsState;
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        executeOnLauncher(launcher -> {
-            RecentsView recentsView = launcher.getOverviewPanel();
+        executeOnRecentsViewContainer(container -> {
+            RecentsView recentsView = container.getOverviewPanel();
             recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true);
         });
     }
 
     @After
     public void tearDown() {
-        executeOnLauncherInTearDown(launcher -> {
-            RecentsView recentsView = launcher.getOverviewPanel();
+        executeOnRecentsViewContainerInTearDown(container -> {
+            RecentsView recentsView = container.getOverviewPanel();
             recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false);
         });
     }
@@ -117,28 +136,27 @@
         startTestAppsWithCheck();
         // mLauncher.pressHome() also tests an important case of pressing home while in background.
         Overview overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(
-                launcher -> assertTrue("Don't have at least 3 tasks", getTaskCount(launcher) >= 3));
+        assertIsInState(
+                "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
+        executeOnRecentsViewContainer(container -> assertTrue(
+                "Don't have at least 3 tasks", getTaskCount(container) >= 3));
 
         // Test flinging forward and backward.
-        executeOnLauncher(launcher -> assertEquals("Current task in Overview is not 0",
-                0, getCurrentOverviewPage(launcher)));
+        executeOnRecentsViewContainer(container -> assertEquals("Current task in Overview is not 0",
+                0, getCurrentOverviewPage(container)));
 
         overview.flingForward();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
         final Integer currentTaskAfterFlingForward = getFromLauncher(
                 launcher -> getCurrentOverviewPage(launcher));
-        executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
-                currentTaskAfterFlingForward > 0));
+        executeOnRecentsViewContainer(container -> assertTrue(
+                "Current task in Overview is still 0", currentTaskAfterFlingForward > 0));
 
         overview.flingBackward();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(launcher -> assertTrue("Flinging back in Overview did nothing",
-                getCurrentOverviewPage(launcher) < currentTaskAfterFlingForward));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
+        executeOnRecentsViewContainer(container -> assertTrue(
+                "Flinging back in Overview did nothing",
+                getCurrentOverviewPage(container) < currentTaskAfterFlingForward));
 
         // Test opening a task.
         OverviewTask task = mLauncher.goHome().switchToOverview().getCurrentTask();
@@ -154,23 +172,22 @@
 
         // Test dismissing a task.
         overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState(
+                "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
         final Integer numTasks = getFromLauncher(launcher -> getTaskCount(launcher));
         task = overview.getCurrentTask();
         assertNotNull("overview.getCurrentTask() returned null (2)", task);
         task.dismiss();
-        executeOnLauncher(
-                launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
-                        numTasks - 1, getTaskCount(launcher)));
+        executeOnRecentsViewContainer(
+                container -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
+                        numTasks - 1, getTaskCount(container)));
 
         // Test dismissing all tasks.
         mLauncher.goHome().switchToOverview().dismissAllTasks();
-        assertTrue("Launcher internal state is not Home",
-                isInState(() -> LauncherState.NORMAL));
-        executeOnLauncher(
-                launcher -> assertEquals("Still have tasks after dismissing all",
-                        0, getTaskCount(launcher)));
+        assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
+        executeOnRecentsViewContainer(
+                container -> assertEquals("Still have tasks after dismissing all",
+                        0, getTaskCount(container)));
     }
 
     /**
@@ -192,12 +209,10 @@
     public void testDismissOverviewWithEscKey() throws Exception {
         startTestAppsWithCheck();
         final Overview overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
 
         overview.dismissByEscKey();
-        assertTrue("Launcher internal state is not Home",
-                isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
     }
 
     @Test
@@ -218,11 +233,9 @@
 
         selectModeButtons.dismissByEscKey();
 
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
         overview.dismissByEscKey();
-        assertTrue("Launcher internal state is not Home",
-                isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
     }
 
     @Test
@@ -230,11 +243,11 @@
         startTestAppsWithCheck();
         startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app.
         Workspace home = mLauncher.goHome();
-        assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher state is not Home", ExpectedState.HOME);
 
         Overview overview = home.openOverviewFromActionPlusTabKeyboardShortcut();
 
-        assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW);
         overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused.
     }
 
@@ -243,28 +256,32 @@
         startTestAppsWithCheck();
         startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app.
         Workspace home = mLauncher.goHome();
-        assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher state is not Home", ExpectedState.HOME);
 
         Overview overview = home.openOverviewFromRecentsKeyboardShortcut();
 
-        assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW);
         overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused.
     }
 
-    private int getCurrentOverviewPage(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
+    private RecentsView getOverviewPanel(RecentsViewContainer recentsViewContainer) {
+        return recentsViewContainer.getOverviewPanel();
     }
 
-    private int getTaskCount(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getTaskViewCount();
+    private int getCurrentOverviewPage(RecentsViewContainer recentsViewContainer) {
+        return getOverviewPanel(recentsViewContainer).getCurrentPage();
     }
 
-    private int getTopRowTaskCountForTablet(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getTopRowTaskCountForTablet();
+    private int getTaskCount(RecentsViewContainer recentsViewContainer) {
+        return getOverviewPanel(recentsViewContainer).getTaskViewCount();
     }
 
-    private int getBottomRowTaskCountForTablet(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getBottomRowTaskCountForTablet();
+    private int getTopRowTaskCountForTablet(RecentsViewContainer recentsViewContainer) {
+        return getOverviewPanel(recentsViewContainer).getTopRowTaskCountForTablet();
+    }
+
+    private int getBottomRowTaskCountForTablet(RecentsViewContainer recentsViewContainer) {
+        return getOverviewPanel(recentsViewContainer).getBottomRowTaskCountForTablet();
     }
 
     @Test
@@ -274,8 +291,8 @@
         startTestAppsWithCheck();
         assertNotNull("Workspace.switchToOverview() returned null",
                 mLauncher.goHome().switchToOverview());
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState(
+                "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
     }
 
     @Test
@@ -300,8 +317,8 @@
 
         assertNotNull("Background.switchToOverview() returned null",
                 launchedAppState.switchToOverview());
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState(
+                "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
     }
 
     private void quickSwitchToPreviousAppAndAssert(boolean toRight) {
@@ -413,11 +430,11 @@
         // Debug if we need to goHome to prevent wrong previous state b/315525621
         mLauncher.goHome();
         mLauncher.getWorkspace().switchToAllApps().pressBackToWorkspace();
-        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+        waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME);
 
         startAppFast(CALCULATOR_APP_PACKAGE);
         mLauncher.getLaunchedAppState().pressBackToWorkspace();
-        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+        waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME);
     }
 
     @Test
@@ -432,16 +449,15 @@
         }
 
         Overview overview = mLauncher.goHome().switchToOverview();
-        executeOnLauncher(
-                launcher -> assertTrue("Don't have at least 13 tasks",
-                        getTaskCount(launcher) >= 13));
+        executeOnRecentsViewContainer(
+                container -> assertTrue("Don't have at least 13 tasks",
+                        getTaskCount(container) >= 13));
 
         // Test scroll the first task off screen
         overview.scrollCurrentTaskOffScreen();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
-                getCurrentOverviewPage(launcher) > 0));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
+        executeOnRecentsViewContainer(container -> assertTrue("Current task in Overview is still 0",
+                getCurrentOverviewPage(container) > 0));
 
         // Test opening the task.
         overview.getCurrentTask().open();
@@ -454,41 +470,41 @@
         // Scroll the task offscreen as it is now first
         overview = mLauncher.goHome().switchToOverview();
         overview.scrollCurrentTaskOffScreen();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
-                getCurrentOverviewPage(launcher) > 0));
+        assertIsInState(
+                "Launcher internal state is not Overview", ExpectedState.OVERVIEW);
+        executeOnRecentsViewContainer(container -> assertTrue("Current task in Overview is still 0",
+                getCurrentOverviewPage(container) > 0));
 
         // Test dismissing the later task.
         final Integer numTasks = getFromLauncher(this::getTaskCount);
         overview.getCurrentTask().dismiss();
-        executeOnLauncher(
-                launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
-                        numTasks - 1, getTaskCount(launcher)));
-        executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after dismissal",
-                (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
-                        launcher)) <= 1)));
+        executeOnRecentsViewContainer(
+                container -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
+                        numTasks - 1, getTaskCount(container)));
+        executeOnRecentsViewContainer(container -> assertTrue(
+                "Grid did not rebalance after dismissal",
+                (Math.abs(getTopRowTaskCountForTablet(container)
+                        - getBottomRowTaskCountForTablet(container)) <= 1)));
 
         // TODO(b/308841019): Re-enable after fixing Overview jank when dismiss
 //        // Test dismissing more tasks.
-//        assertTrue("Launcher internal state didn't remain in Overview",
-//                isInState(() -> LauncherState.OVERVIEW));
+//        assertIsInState(
+//                "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
 //        overview.getCurrentTask().dismiss();
-//        assertTrue("Launcher internal state didn't remain in Overview",
-//                isInState(() -> LauncherState.OVERVIEW));
+//        assertIsInState(
+//                "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
 //        overview.getCurrentTask().dismiss();
-//        executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple
-//        dismissals",
-//                (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
-//                        launcher)) <= 1)));
+//        executeOnRecentsViewContainer(container -> assertTrue(
+//        "Grid did not rebalance after multiple dismissals",
+//                (Math.abs(getTopRowTaskCountForTablet(container)
+//                        - getBottomRowTaskCountForTablet(container)) <= 1)));
 
         // Test dismissing all tasks.
         mLauncher.goHome().switchToOverview().dismissAllTasks();
-        assertTrue("Launcher internal state is not Home",
-                isInState(() -> LauncherState.NORMAL));
-        executeOnLauncher(
-                launcher -> assertEquals("Still have tasks after dismissing all",
-                        0, getTaskCount(launcher)));
+        assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
+        executeOnRecentsViewContainer(
+                container -> assertEquals("Still have tasks after dismissing all",
+                        0, getTaskCount(container)));
     }
 
     @Test
@@ -497,22 +513,19 @@
         startTestAppsWithCheck();
 
         Overview overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state should be Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(
-                launcher -> assertTrue("Should have at least 3 tasks",
-                        getTaskCount(launcher) >= 3));
+        assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
+        executeOnRecentsViewContainer(
+                container -> assertTrue("Should have at least 3 tasks",
+                        getTaskCount(container) >= 3));
 
         // It should not dismiss overview when tapping between tasks
         overview.touchBetweenTasks();
         overview = mLauncher.getOverview();
-        assertTrue("Launcher internal state should be Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
 
         // Dismiss when tapping to the right of the focused task
         overview.touchOutsideFirstTask();
-        assertTrue("Launcher internal state should be Home",
-                isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher internal state should be Home", ExpectedState.HOME);
     }
 
     @Test
@@ -524,34 +537,29 @@
         startTestAppsWithCheck();
 
         Overview overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state should be Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(
-                launcher -> assertTrue("Should have at least 3 tasks",
-                        getTaskCount(launcher) >= 3));
+        assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
+        executeOnRecentsViewContainer(
+                container -> assertTrue("Should have at least 3 tasks",
+                        getTaskCount(container) >= 3));
 
         if (mLauncher.isTransientTaskbar()) {
             // On transient taskbar, it should dismiss when tapping outside taskbar bounds.
             overview.touchTaskbarBottomCorner(/* tapRight= */ false);
-            assertTrue("Launcher internal state should be Normal",
-                    isInState(() -> LauncherState.NORMAL));
+            assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME);
 
             overview = mLauncher.getWorkspace().switchToOverview();
 
             // On transient taskbar, it should dismiss when tapping outside taskbar bounds.
             overview.touchTaskbarBottomCorner(/* tapRight= */ true);
-            assertTrue("Launcher internal state should be Normal",
-                    isInState(() -> LauncherState.NORMAL));
+            assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME);
         } else {
             // On persistent taskbar, it should not dismiss when tapping the taskbar
             overview.touchTaskbarBottomCorner(/* tapRight= */ false);
-            assertTrue("Launcher internal state should be Overview",
-                    isInState(() -> LauncherState.OVERVIEW));
+            assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
 
             // On persistent taskbar, it should not dismiss when tapping the taskbar
             overview.touchTaskbarBottomCorner(/* tapRight= */ true);
-            assertTrue("Launcher internal state should be Overview",
-                    isInState(() -> LauncherState.OVERVIEW));
+            assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
         }
     }
 
@@ -594,4 +602,28 @@
             // Presumably the test started with 0 tasks and remains that way after going home.
         }
     }
+
+    private void assertIsInState(
+            @NonNull String failureMessage, @NonNull ExpectedState expectedState) {
+        assertTrue(failureMessage, enableLauncherOverviewInWindow()
+                ? isInRecentsWindowState(() -> expectedState.mRecentsState)
+                : isInState(() -> expectedState.mLauncherState));
+    }
+
+    private void waitForState(
+            @NonNull String failureMessage, @NonNull ExpectedState expectedState) {
+        if (enableLauncherOverviewInWindow()) {
+            waitForRecentsWindowState(failureMessage, () -> expectedState.mRecentsState);
+        } else {
+            waitForState(failureMessage, () -> expectedState.mLauncherState);
+        }
+    }
+
+    private void executeOnRecentsViewContainer(@NonNull Consumer<RecentsViewContainer> f) {
+        if (enableLauncherOverviewInWindow()) {
+            executeOnRecentsWindow(f::accept);
+        } else {
+            executeOnLauncher(f::accept);
+        }
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java b/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
index 633a575..960a467 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
@@ -28,8 +28,9 @@
 import android.content.Intent;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.quickstep.fallback.window.RecentsWindowManager;
+import com.android.quickstep.fallback.window.RecentsWindowFactory;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -40,21 +41,29 @@
 @SmallTest
 public class TaskAnimationManagerTest {
 
-    @Mock
-    private Context mContext;
+    protected final Context mContext =
+            InstrumentationRegistry.getInstrumentation().getTargetContext();
 
     @Mock
-    private RecentsWindowManager mRecentsWindowManager;
+    private RecentsWindowFactory mRecentsWindowFactory;
 
     @Mock
     private SystemUiProxy mSystemUiProxy;
 
     private TaskAnimationManager mTaskAnimationManager;
+    protected RecentsAnimationDeviceState mRecentsAnimationDeviceState;
+
+    @Before
+    public void setUpRecentsAnimationDeviceState() {
+        runOnMainSync(() ->
+                mRecentsAnimationDeviceState = new RecentsAnimationDeviceState(mContext, true));
+    }
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mTaskAnimationManager = new TaskAnimationManager(mContext, mRecentsWindowManager) {
+        mTaskAnimationManager = new TaskAnimationManager(mContext, mRecentsWindowFactory,
+                mRecentsAnimationDeviceState) {
             @Override
             SystemUiProxy getSystemUiProxy() {
                 return mSystemUiProxy;
@@ -69,8 +78,8 @@
         final RecentsAnimationCallbacks.RecentsAnimationListener listener =
                 mock(RecentsAnimationCallbacks.RecentsAnimationListener.class);
         doReturn(activityInterface).when(gestureState).getContainerInterface();
-        mTaskAnimationManager.startRecentsAnimation(gestureState, new Intent(), listener);
-
+        runOnMainSync(() ->
+                mTaskAnimationManager.startRecentsAnimation(gestureState, new Intent(), listener));
         final ArgumentCaptor<ActivityOptions> optionsCaptor =
                 ArgumentCaptor.forClass(ActivityOptions.class);
         verify(mSystemUiProxy)
@@ -78,4 +87,8 @@
         assertEquals(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS,
                 optionsCaptor.getValue().getPendingIntentBackgroundActivityStartMode());
     }
+
+    protected static void runOnMainSync(Runnable runnable) {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
+    }
 }
diff --git a/res/drawable/ic_desktop_add.xml b/res/drawable/ic_desktop_add.xml
new file mode 100644
index 0000000..fa5e0de
--- /dev/null
+++ b/res/drawable/ic_desktop_add.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960">
+    <path
+        android:pathData="M140,740v-520,520ZM240,640v-200h360v200L240,640ZM140,800q-24,0 -42,-18t-18,-42v-520q0,-24 18,-42t42,-18h680q24,0 42,18t18,42v300h-60v-300L140,220v520h420v60L140,800ZM680,520v-160L360,360v-40h360v200h-40ZM740,880v-120L620,760v-60h120v-120h60v120h120v60L800,760v120h-60Z"
+        android:fillColor="#5f6368"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/inset_rounded_action_button.xml b/res/drawable/inset_rounded_action_button.xml
new file mode 100644
index 0000000..8ae40c0
--- /dev/null
+++ b/res/drawable/inset_rounded_action_button.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    android:insetTop="@dimen/inset_rounded_action_button"
+    android:insetBottom="@dimen/inset_rounded_action_button"
+    android:insetLeft="@dimen/inset_rounded_action_button"
+    android:insetRight="@dimen/inset_rounded_action_button">
+    <shape
+        android:shape="rectangle">
+        <solid android:color="@color/materialColorSurfaceContainerLow" />
+        <corners android:radius="@dimen/rounded_button_radius" />
+        <stroke
+            android:width="1dp"
+            android:color="@color/materialColorSurfaceContainerLow" />
+    </shape>
+</inset>
diff --git a/res/layout/work_apps_edu.xml b/res/layout/work_apps_edu.xml
index a45d585..0e2c19a 100644
--- a/res/layout/work_apps_edu.xml
+++ b/res/layout/work_apps_edu.xml
@@ -25,9 +25,8 @@
         android:orientation="horizontal"
         android:background="@drawable/work_card"
         android:layout_gravity="center_horizontal"
-        android:paddingEnd="@dimen/work_card_margin"
         android:paddingStart="@dimen/work_card_margin"
-        android:paddingTop="@dimen/work_card_margin"
+        android:paddingEnd="@dimen/work_card_margin_end"
         android:paddingBottom="@dimen/work_card_margin"
         android:id="@+id/wrapper">
         <TextView
@@ -37,18 +36,22 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
+            android:layout_marginTop="@dimen/work_card_margin"
             android:paddingEnd="@dimen/work_edu_card_text_end_margin"
             android:text="@string/work_profile_edu_work_apps"
             android:textDirection="locale"
             android:textSize="18sp" />
         <FrameLayout
+            android:id="@+id/action_btn"
             android:layout_width="@dimen/rounded_button_width"
             android:layout_height="@dimen/rounded_button_width"
-            android:background="@drawable/rounded_action_button">
+            android:layout_marginTop="@dimen/work_edu_card_button_margin_top"
+            android:gravity="center"
+            android:background="@drawable/inset_rounded_action_button">
             <ImageButton
-                android:id="@+id/action_btn"
                 android:layout_width="@dimen/x_icon_size"
                 android:layout_height="@dimen/x_icon_size"
+                android:clickable="false"
                 android:scaleType="centerInside"
                 android:layout_gravity="center"
                 android:contentDescription="@string/accessibility_close"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index d28e1f5..716f5dc 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Werk"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Gesprekke"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Neem notas"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Voeg by"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Voeg <xliff:g id="WIDGET_NAME">%1$s</xliff:g>-legstuk by"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Wys almal"</string>
@@ -101,9 +105,9 @@
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"installeer kortpaaie"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Laat \'n app toe om kortpaaie by te voeg sonder gebruikerinmenging."</string>
     <string name="permlab_read_settings" msgid="5136500343007704955">"lees tuis-instellings en -kortpaaie"</string>
-    <string name="permdesc_read_settings" msgid="4208061150510996676">"Laat die program toe om die instellings en kortpaaie op tuisskerm te lees."</string>
+    <string name="permdesc_read_settings" msgid="4208061150510996676">"Laat die app toe om die instellings en kortpaaie op tuisskerm te lees."</string>
     <string name="permlab_write_settings" msgid="4820028712156303762">"skryf tuis-instellings en -kortpaaie"</string>
-    <string name="permdesc_write_settings" msgid="726859348127868466">"Laat die program toe om die instellings en kortpaaie op tuisskerm te verander."</string>
+    <string name="permdesc_write_settings" msgid="726859348127868466">"Laat die app toe om die instellings en kortpaaie op tuisskerm te verander."</string>
     <string name="gadget_error_text" msgid="740356548025791839">"Kan nie legstuk laai nie"</string>
     <string name="gadget_setup_text" msgid="8348374825537681407">"Legstukinstellings"</string>
     <string name="gadget_complete_setup_text" msgid="309040266978007925">"Tik om opstelling te voltooi"</string>
@@ -151,7 +155,7 @@
     <string name="app_archived_title" msgid="4548283110222420708">"<xliff:g id="NAME">%1$s</xliff:g> is geargiveer."</string>
     <string name="app_unarchiving_action" msgid="5736107006413929484">"laai af en stel terug"</string>
     <string name="dialog_update_title" msgid="114234265740994042">"Programopdatering word vereis"</string>
-    <string name="dialog_update_message" msgid="4176784553982226114">"Die program vir hierdie ikoon is nie opgedateer nie. Jy kan dit handmatig opdateer om hierdie kortpad weer te aktiveer, of die ikoon verwyder."</string>
+    <string name="dialog_update_message" msgid="4176784553982226114">"Die app vir hierdie ikoon is nie opgedateer nie. Jy kan dit handmatig opdateer om hierdie kortpad weer te aktiveer, of die ikoon verwyder."</string>
     <string name="dialog_update" msgid="2178028071796141234">"Dateer op"</string>
     <string name="dialog_remove" msgid="6510806469849709407">"Verwyder"</string>
     <string name="widgets_list" msgid="796804551140113767">"Legstukkelys"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 6ed533e..1f7f5f7 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ሥራ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"ውይይቶች"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"የማስታወሻ አያያዝ"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"አክል"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"ምግብር <xliff:g id="WIDGET_NAME">%1$s</xliff:g>ን አክል"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"ሁሉንም አሳይ"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index e924f4d..bbdfebd 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"تطبيقات العمل"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"المحادثات"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"تدوين الملاحظات"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"إضافة"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"إضافة التطبيق المصغّر \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\""</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"عرض الكل"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 1e88444..44cb92f 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"কৰ্মস্থান"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"বাৰ্তালাপ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"টোকা গ্ৰহণ কৰা"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"যোগ দিয়ক"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ৱিজেট যোগ দিয়ক"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"আটাইবোৰ দেখুৱাওক"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 516b401..f2bf5f7 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"İş"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Söhbətlər"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Qeydgötürmə"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Əlavə edin"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> vidcet əlavə edin"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Hamısını göstər"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index c8aaa88..53b1a02 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Posao"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Konverzacije"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Pravljenje beležaka"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Dodaj"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodajte vidžet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Prikaži sve"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index b2f2f32..586a44a 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Працоўныя"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Размовы"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Стварэнне нататак"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Дадаць"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Дадаць віджэт \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\""</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Паказаць усе"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index f4fb396..ea53cef 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Служебни"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Разговори"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Водене на бележки"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Добавяне"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Добавяне на приспособлението „<xliff:g id="WIDGET_NAME">%1$s</xliff:g>“"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Вижте всички"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 1d38e66..3e71dc8 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"অফিস"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"কথোপকথন"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"নোট নেওয়া"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"যোগ করুন"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> উইজেট যোগ করুন"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"সব দেখুন"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 57b475c..5173061 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Posao"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Razgovori"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Pisanje bilješki"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Dodajte"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodavanje vidžeta <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Prikaži sve"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 5c7409f..7226581 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Treball"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Converses"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Presa de notes"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Afegeix"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Afegeix el widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostra-ho tot"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index e5f26da..51fb179 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Práce"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Konverzace"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Psaní poznámek"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Přidat"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Přidat widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Zobrazit vše"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 1fb015f..4b4664a 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Arbejde"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Samtaler"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Notetagning"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Tilføj"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Tilføj <xliff:g id="WIDGET_NAME">%1$s</xliff:g>-widget"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Vis alle"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 6eb50a7..4fe0def 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Geschäftlich"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Unterhaltungen"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Notizen"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Hinzufügen"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Widget „<xliff:g id="WIDGET_NAME">%1$s</xliff:g>“ hinzufügen"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Alle anzeigen"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index deaf4ff..3a6d849 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Εργασίας"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Συζητήσεις"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Δημιουργία σημειώσεων"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Προσθήκη"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Προσθήκη του γραφικού στοιχείου <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Εμφάνιση όλων"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 1175601..fb5466b 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Work"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Note-taking"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Add"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Add <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Show all"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 1dc6d16..363bb4b 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -68,6 +68,8 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Work"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Note-taking"</string>
+    <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Show add button"</string>
+    <string name="widget_cell_tap_to_hide_add_button_label" msgid="6117805205101555997">"Hide add button"</string>
     <string name="widget_add_button_label" msgid="2761267068711937179">"Add"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Add <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Show all"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 1175601..fb5466b 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Work"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Note-taking"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Add"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Add <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Show all"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 1175601..fb5466b 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Work"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Note-taking"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Add"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Add <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Show all"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 7006bfb..c522d08 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabajo"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversaciones"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Tomar notas"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Agregar"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Agregar widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostrar todos"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index dabba16..fd11b09 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabajo"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversaciones"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Toma de notas"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Añadir"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Añadir widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostrar todo"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 72f6bb8..0bc557f 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Töö"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Vestlused"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Märkmete tegemine"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Lisa"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Lisa vidin <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Kuva kõik"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 37d3e4f..4e69c3d 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Lanekoak"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Elkarrizketak"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Oharrak idazteko"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Gehitu"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Gehitu <xliff:g id="WIDGET_NAME">%1$s</xliff:g> widgeta"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Erakutsi guztiak"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 11130e4..a772f0c 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ابزاره‌های کاری"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"مکالمه‌ها"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"یادداشت‌برداری"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"افزودن"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"افزودن ابزاره <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"نمایش همه"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 3a25c51..7c7045f 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Työ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Keskustelut"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Muistiinpanojen tekeminen"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Lisää"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Lisää widget: <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Näytä kaikki"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 45a0403..8209d3a 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Professionnels"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Prise de note"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Ajouter"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Ajoutez le widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Tout afficher"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 0a61189..f2517ff 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Professionnels"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversations"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Prise de notes"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Ajouter"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Ajoutez un widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Tout afficher"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 293a6b3..8945603 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Widgets do traballo"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversas"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Toma de notas"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Engadir"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Engadir o widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostrar todo"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index c7d3a10..0f3f265 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ઑફિસ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"વાતચીતો"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"નોંધ લેવી"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"ઉમેરો"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> વિજેટ ઉમેરો"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"બધા બતાવો"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 1bf63d6..28a29ca 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"वर्क विजेट"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"बातचीत"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"नोट बनाने से जुड़े विजेट"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"जोड़ें"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट जोड़ें"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"सभी दिखाएं"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 7bf6f33..79726e4 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Posao"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Razgovori"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Pisanje bilježaka"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Dodaj"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodaj widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Prikaži sve"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index e82566e..154cac0 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Munka"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Beszélgetések"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Jegyzetelés"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Hozzáadás"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> modul hozzáadása"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Az összes megjelenítése"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 4879d4d..85a1f28 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Աշխատանքային"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Զրույցներ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Նշումների ստեղծում"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Ավելացնել"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Ավելացնել <xliff:g id="WIDGET_NAME">%1$s</xliff:g> վիջեթը"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Բոլորը"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index dde261a..27079dd 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Kerja"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Percakapan"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Pembuatan catatan"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Tambahkan"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Tambahkan widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Tampilkan semua"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 0698bab..0673881 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Vinna"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Samtöl"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Glósugerð"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Bæta við"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Bæta græjunni <xliff:g id="WIDGET_NAME">%1$s</xliff:g> við"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Sýna allt"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index fdd62a4..97441a0 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Lavoro"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversazioni"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Aggiunta di note"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Aggiungi"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Aggiungi widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostra tutto"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index cfc0291..adf1ad4 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ווידג\'טים לעבודה"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"שיחות"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"כתיבת הערות"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"הוספה"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"הוספת הווידג\'ט <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"הצגת הכול"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 01626ab..7045661 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"仕事用"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"会話"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"メモ"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"追加"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>ウィジェットを追加"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"すべて表示"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 536c1ad..30aa835 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"სამსახური"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"მიმოწერები"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"ჩანიშვნა"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"დამატება"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ვიჯეტის დამატება"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"ყველას ჩვენება"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index b7f38b9..2317944 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Жұмыс виджеттері"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Әңгімелер"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Ескертпе жазу"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Қосу"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Виджет (<xliff:g id="WIDGET_NAME">%1$s</xliff:g>) қосу"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Барлығын көру"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 6a707ae..cfc2932 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ការងារ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"ការសន្ទនា"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"ការកត់ត្រា"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"បញ្ចូល"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"បញ្ចូលធាតុ​ក្រាហ្វិក <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"បង្ហាញ​ទាំងអស់"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 0ce1a0f..a1daac0 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ಕೆಲಸ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"ಸಂಭಾಷಣೆಗಳು"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"ಟಿಪ್ಪಣಿ ತೆಗೆದುಕೊಳ್ಳುವುದು"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"ಸೇರಿಸಿ"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ವಿಜೆಟ್ ಸೇರಿಸಿ"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"ಎಲ್ಲಾ ತೋರಿಸಿ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 19e75d1..db40d20 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"직장 위젯"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"대화"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"메모"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"추가"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> 위젯 추가"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"모두 표시"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 901b2bd..eb46e16 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Жумуш"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Сүйлөшүүлөр"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Эскертме жазуу"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Кошуу"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджетин кошуу"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Баарын көрсөтүү"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index f01dc67..89bb218 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ວຽກ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"ການສົນທະນາ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"ການຈົດບັນທຶກ"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"ເພີ່ມ"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"ເພີ່ມວິດເຈັດ <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"ສະແດງທັງໝົດ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index f5e21de..839c876 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Darbas"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Pokalbiai"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Užrašų kūrimas"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Pridėti"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Pridėti valdiklį: <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Rodyti viską"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 3eafd9a..8620b7b 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Darba"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Sarunas"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Piezīmju pierakstīšana"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Pievienot"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Pievienot logrīku <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Rādīt visus"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 2b85e12..2685c60 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Работни"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Разговори"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Фаќање белешки"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Додај"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Додај го виџетот <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Прикажи ги сите"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index df98b72..b9da880 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ജോലി"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"സംഭാഷണങ്ങൾ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"കുറിപ്പ് രേഖപ്പെടുത്തൽ"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"ചേർക്കുക"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> വിജറ്റ് ചേർക്കുക"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"എല്ലാം കാണിക്കൂ"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 9fdaf13..8197832 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Ажил"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Харилцан яриа"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Тэмдэглэл хөтлөх"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Нэмэх"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджетийг нэмэх"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Бүгдийг харуул"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index e596193..5281353 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ऑफिस"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"संभाषणे"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"टिपा घेणे"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"जोडा"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट जोडा"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"सर्व दाखवा"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 91c85de..5592cd5 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Kerja"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Perbualan"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Pengambilan nota"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Tambah"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Tambahkan widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Tunjukkan semua"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 8ed7980..c7bb075 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"အလုပ်"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"စကားဝိုင်းများ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"မှတ်စုလိုက်ခြင်း"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"ထည့်ရန်"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ဝိဂျက်ထည့်ရန်"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"အားလုံးပြပါ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index fa7a047..c9b8de0 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Jobb"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Samtaler"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Notatskriving"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Legg til"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Legg til <xliff:g id="WIDGET_NAME">%1$s</xliff:g>-modulen"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Vis alle"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 86ebc7b..a966f9e 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"कामसम्बन्धी"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"वार्तालापहरू"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"नोट लेख्ने कार्य"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"हाल्नुहोस्"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट हाल्नुहोस्"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"सबै देखाउनुहोस्"</string>
diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml
index d9f9769..4a6340a 100644
--- a/res/values-night-v31/colors.xml
+++ b/res/values-night-v31/colors.xml
@@ -47,7 +47,7 @@
     <color name="widget_picker_unselected_tab_text_color_dark">
         @android:color/system_neutral2_200</color>
     <color name="widget_picker_collapse_handle_color_dark">
-        @android:color/system_neutral2_700</color>
+        @android:color/system_neutral2_400</color>
     <color name="widget_picker_add_button_background_color_dark">
         @android:color/system_accent1_200</color>
     <color name="widget_picker_add_button_text_color_dark">
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 1e3e166..1c819fc 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Werk"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Gesprekken"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Aantekeningen maken"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Toevoegen"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> toevoegen"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Alles tonen"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index f8d68ae..14c9617 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ୱାର୍କ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"ନୋଟ-ଟେକିଂ"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"ଯୋଗ କରନ୍ତୁ"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"ସବୁ ଦେଖାନ୍ତୁ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 07aae76..2882620 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ਕੰਮ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"ਗੱਲਾਂਬਾਤਾਂ"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"ਨੋਟ ਬਣਾਉਣਾ"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"ਸਭ ਦਿਖਾਓ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index f76e37b..ae4ff2f 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Służbowe"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Rozmowy"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Notatki"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Dodaj"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodaj widżet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Pokaż wszystko"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 66dc18f..cd3f2a6 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabalho"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversas"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Tomar notas"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Adicionar"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Adicione o widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostrar tudo"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index b9ead80..f40c5b3 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabalho"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversas"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Anotações"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Adicionar"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Adicionar o widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Mostrar tudo"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 73a5ffb..5a98b55 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Serviciu"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversații"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Luare de notițe"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Adaugă"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Adaugă widgetul <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Afișează tot"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 54a1024..2437f29 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Рабочие виджеты"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Разговоры"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Создание заметок"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Добавить"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Добавить виджет \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\""</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Показать все"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 21ac9ac..2277d24 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"කාර්යාලය"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"සංවාද"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"සටහන් කර ගැනීම"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"එක් කරන්න"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> විජට්ටුව එක් කරන්න"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"සියල්ල පෙන්වන්න"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index bf96af6..37e2a7e 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Pracovné"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Konverzácie"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Zapisovanie poznámok"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Pridať"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Pridať miniaplikáciu <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Zobraziť všetko"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index b7ecd50..6072e9f 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Služba"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Pogovori"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Ustvarjanje zapiskov"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Dodaj"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Dodajanje pripomočka »<xliff:g id="WIDGET_NAME">%1$s</xliff:g>«"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Pokaži vse"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 64bb03f..995db0f 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Puna"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Bisedat"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Mbajtja e shënimeve"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Shto"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Shto miniaplikacionin <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Shfaq të gjitha"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 44bc9b5..ee69f34 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Посао"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Конверзације"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Прављење бележака"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Додај"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Додајте виџет <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Прикажи све"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 217e116..0aeac3b 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Arbete"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Konversationer"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Anteckna"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Lägg till"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Lägg till widgeten <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Visa alla"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 4a2db28..674e8f9 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Kazini"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Mazungumzo"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Kuandika madokezo"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Weka"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Weka wijeti ya <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Onyesha zote"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index e724f36..6287607 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"பணி"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"உரையாடல்கள்"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"குறிப்பெடுத்தல்"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"சேர்"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> விட்ஜெட்டைச் சேர்க்கும்"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"எல்லாம் காட்டு"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 186e94d..ba7bf2c 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ఆఫీస్"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"సంభాషణలు"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"నోట్-టేకింగ్"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"జోడించండి"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> విడ్జెట్‌ను జోడించండి"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"అన్నీ చూడండి"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index b5373f6..0338163 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"งาน"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"การสนทนา"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"การจดบันทึก"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"เพิ่ม"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"เพิ่มวิดเจ็ต <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"แสดงทั้งหมด"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 7bc7f9d..4bd5c58 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabaho"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Mga Pag-uusap"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Pagtatala"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Idagdag"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Idagdag ang widget na <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Ipakita lahat"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 9990967..c11a5cd 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"İş"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Görüşmeler"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Not alma"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Ekle"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget\'ı ekle"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Tümünü göster"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 165ab49..da56b6c 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Робочі"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Розмови"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Створення нотаток"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Додати"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Додати віджет \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\""</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Показати всі"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 1bd2e4a..f978e2e 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"دفتری ویجیٹس"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"گفتگوئیں"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"نوٹ لکھنا"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"شامل کریں"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ویجیٹ شامل کریں"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"سبھی دکھائیں"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index b9286ec..371eece 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Ish"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Suhbatlar"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Qayd olish"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Chiqarish"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> vidjetini chiqarish"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Hammasi"</string>
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index d74e308..8b43f20 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -98,7 +98,7 @@
     <color name="widget_picker_unselected_tab_text_color_light">
         @android:color/system_neutral2_700</color>
     <color name="widget_picker_collapse_handle_color_light">
-        @android:color/system_neutral2_200</color>
+        @android:color/system_neutral2_500</color>
     <color name="widget_picker_add_button_background_color_light">
         @android:color/system_accent1_600</color>
     <color name="widget_picker_add_button_text_color_light">
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 6082ef6..94fef7e 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Công việc"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Cuộc trò chuyện"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Ghi chú"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Thêm"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Thêm tiện ích <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Hiện tất cả"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index c1917a9..6492e2b 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"工作"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"对话"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"记事"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"添加"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"添加“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"全部显示"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index d29653c..d60bc6d 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"工作"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"對話"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"做筆記"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"新增"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"加<xliff:g id="WIDGET_NAME">%1$s</xliff:g>小工具"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"顯示全部"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index eaa5a73..618fb90 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"工作"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"對話"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"做筆記"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"新增"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"新增「<xliff:g id="WIDGET_NAME">%1$s</xliff:g>」小工具"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"全部顯示"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 60dffeb..2b102b0 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -68,6 +68,10 @@
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Umsebenzi"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Izingxoxo"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Ukuthatha amanothi"</string>
+    <!-- no translation found for widget_cell_tap_to_show_add_button_label (4354194214317043581) -->
+    <skip />
+    <!-- no translation found for widget_cell_tap_to_hide_add_button_label (6117805205101555997) -->
+    <skip />
     <string name="widget_add_button_label" msgid="2761267068711937179">"Engeza"</string>
     <string name="widget_add_button_content_description" msgid="1810530016360039643">"Engeza iwijethi ye-<xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"Bonisa konke"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index c477633..698877a 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -162,10 +162,11 @@
         <attr name="layout_sticky" format="boolean" />
     </declare-styleable>
 
-    <declare-styleable name="GridDimension">
-        <attr name="minDeviceWidthDp" format="integer"/>
-        <attr name="minDeviceHeightDp" format="integer"/>
-        <attr name="numGridDimension" format="integer"/>
+    <declare-styleable name="GridSize">
+        <attr name="minDeviceWidthDp" format="float"/>
+        <attr name="minDeviceHeightDp" format="float"/>
+        <attr name="numGridRows" format="integer"/>
+        <attr name="numGridColumns" format="integer"/>
         <attr name="dbFile" />
         <attr name="defaultLayoutId"/>
         <attr name="demoModeLayoutId"/>
@@ -261,7 +262,7 @@
         <!-- File that contains the specs for all apps icon and text size.
         Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
         <attr name="allAppsCellSpecsId" format="reference" />
-        <attr name="rowCountSpecsId" format="reference" />
+        <attr name="gridSizeSpecsId" format="reference" />
         <!-- defaults to allAppsCellSpecsId, if not specified -->
         <attr name="allAppsCellSpecsTwoPanelId" format="reference" />
         <!-- defaults to false, if not specified -->
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 967d97d..914ffd6 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -114,7 +114,7 @@
     <color name="widget_picker_tab_background_unselected_light">#E3E3E3</color>
     <color name="widget_picker_selected_tab_text_color_light">#FFFFFF</color>
     <color name="widget_picker_unselected_tab_text_color_light">#444746</color>
-    <color name="widget_picker_collapse_handle_color_light">#C4C7C5</color>
+    <color name="widget_picker_collapse_handle_color_light">#747775</color>
     <color name="widget_picker_add_button_background_color_light">#0B57D0</color>
     <color name="widget_picker_add_button_text_color_light">#0B57D0</color>
     <color name="widget_picker_expand_button_background_color_light">
@@ -141,7 +141,7 @@
     <color name="widget_picker_tab_background_unselected_dark">#343535</color>
     <color name="widget_picker_selected_tab_text_color_dark">#2D312F</color>
     <color name="widget_picker_unselected_tab_text_color_dark">#C4C7C5</color>
-    <color name="widget_picker_collapse_handle_color_dark">#444746</color>
+    <color name="widget_picker_collapse_handle_color_dark">#8e918f</color>
     <color name="widget_picker_add_button_background_color_dark">#062E6F</color>
     <color name="widget_picker_add_button_text_color_dark">#FFFFFF</color>
     <color name="widget_picker_expand_button_background_color_dark">
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 326ee06..21aabfa 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -173,13 +173,16 @@
     <dimen name="work_edu_card_margin">16dp</dimen>
     <dimen name="work_edu_card_radius">16dp</dimen>
     <dimen name="work_edu_card_bottom_margin">26dp</dimen>
-    <dimen name="work_edu_card_text_end_margin">32dp</dimen>
+    <dimen name="work_edu_card_text_end_margin">12dp</dimen>
     <dimen name="work_apps_paused_button_stroke">1dp</dimen>
+    <dimen name="work_edu_card_button_margin_top">12dp</dimen>
 
     <dimen name="work_card_margin">24dp</dimen>
+    <dimen name="work_card_margin_end">12dp</dimen>
     <!-- (x) icon button inside work edu card -->
-    <dimen name="rounded_button_width">24dp</dimen>
+    <dimen name="rounded_button_width">48dp</dimen>
     <dimen name="x_icon_size">16dp</dimen>
+    <dimen name="inset_rounded_action_button">12dp</dimen>
 
     <!-- rounded button shown inside card views, and snack bars  -->
     <dimen name="padded_rounded_button_height">48dp</dimen>
@@ -297,10 +300,6 @@
     <!-- the distance an icon must be dragged before button drop targets accept it -->
     <dimen name="drag_distanceThreshold">30dp</dimen>
 
-    <!-- Elevation for the drag view. It should be larger than elevation of all other drag sources
-         and drop targets like all-apps and folders -->
-    <dimen name="drag_elevation">30dp</dimen>
-
     <dimen name="drag_flingToDeleteMinVelocity">-1500dp</dimen>
 
     <dimen name="spring_loaded_panel_border">2dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f7069a6..cdfbefe 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -154,6 +154,10 @@
     <!-- A widget category label for grouping widgets related to note taking. [CHAR_LIMIT=30] -->
     <string name="widget_category_note_taking">Note-taking</string>
 
+    <!-- Accessibility label on the widget preview that on click (if add button is hidden) shows the button to add widget to the home screen. [CHAR_LIMIT=none] -->
+    <string name="widget_cell_tap_to_show_add_button_label">Show add button</string>
+    <!-- Accessibility label on the widget preview that on click (if add button is showing) hides the button to add widget to the home screen. [CHAR_LIMIT=none] -->
+    <string name="widget_cell_tap_to_hide_add_button_label">Hide add button</string>
     <!-- Text on the button that adds a widget to the home screen. [CHAR_LIMIT=15] -->
     <string name="widget_add_button_label">Add</string>
     <!-- Accessibility content description for the button that adds a widget to the home screen. The
diff --git a/res/xml/backupscheme.xml b/res/xml/backupscheme.xml
index 34b80b1..083af5c 100644
--- a/res/xml/backupscheme.xml
+++ b/res/xml/backupscheme.xml
@@ -11,6 +11,7 @@
     <include domain="database" path="launcher_3_by_3.db" />
     <include domain="database" path="launcher_2_by_2.db" />
     <include domain="database" path="launcher_7_by_3.db" />
+    <include domain="database" path="launcher_8_by_3.db" />
     <include domain="sharedpref" path="com.android.launcher3.prefs.xml" />
     <include domain="file" path="downgrade_schema.json" />
 
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 50e78ac..3b93cf4 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -80,12 +80,16 @@
         updateTheme();
     }
 
-    protected void updateTheme() {
+    private void updateTheme() {
         if (mThemeRes != Themes.getActivityThemeRes(this)) {
-            recreate();
+            recreateToUpdateTheme();
         }
     }
 
+    protected void recreateToUpdateTheme() {
+        recreate();
+    }
+
     @Override
     public void onActionModeStarted(ActionMode mode) {
         super.onActionModeStarted(mode);
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index df5f520..3d71ff1 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -1816,7 +1816,8 @@
                 + (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
         final int vStartPadding = getPaddingTop();
 
-        int x = hStartPadding + (cellX * mBorderSpace.x) + (cellX * cellWidth);
+        int x = hStartPadding + (cellX * mBorderSpace.x) + (cellX * cellWidth)
+                + getTranslationXForCell(cellX, cellY);
         int y = vStartPadding + (cellY * mBorderSpace.y) + (cellY * cellHeight);
 
         int width = cellHSpan * cellWidth + ((cellHSpan - 1) * mBorderSpace.x);
@@ -1825,6 +1826,11 @@
         resultRect.set(x, y, x + width, y + height);
     }
 
+    /** Enables successors to provide an X adjustment for the cell. */
+    protected int getTranslationXForCell(int cellX, int cellY) {
+        return 0;
+    }
+
     public void markCellsAsOccupiedForView(View view) {
         if (view instanceof LauncherAppWidgetHostView
                 && view.getTag() instanceof LauncherAppWidgetInfo) {
diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java
index 3e4e96b..e9cd16c 100644
--- a/src/com/android/launcher3/CheckLongPressHelper.java
+++ b/src/com/android/launcher3/CheckLongPressHelper.java
@@ -151,6 +151,8 @@
                 handled = mView.performLongClick();
             }
             if (handled) {
+                // Cancel any default long-press action on the view
+                mView.cancelLongPress();
                 mView.setPressed(false);
                 mHasPerformedLongPress = true;
             }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 9b6fe4e..f1274dc 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -425,7 +425,9 @@
                 && WindowManagerProxy.INSTANCE.get(context).isTaskbarDrawnInProcess();
 
         // Some more constants.
-        context = getContext(context, info, isVerticalBarLayout() || (isTablet && isLandscape)
+        context = getContext(context, info, inv.isFixedLandscape
+                        || isVerticalBarLayout()
+                        || (isTablet && isLandscape)
                         ? Configuration.ORIENTATION_LANDSCAPE
                         : Configuration.ORIENTATION_PORTRAIT,
                 windowBounds);
@@ -865,7 +867,7 @@
         canQsbInline = canQsbInline && hotseatQsbHeight > 0;
 
         return (mIsScalableGrid && inv.inlineQsb[mTypeIndex] && canQsbInline)
-                || inv.isFixedLandscapeMode;
+                || inv.isFixedLandscape;
     }
 
     private static DotRenderer createDotRenderer(
@@ -1834,7 +1836,7 @@
             }
             int paddingTop = workspaceTopPadding + (mIsScalableGrid ? 0 : edgeMarginPx);
             // On isFixedLandscapeMode on phones we already have padding because of the camera hole
-            int paddingSide = inv.isFixedLandscapeMode ? 0 : desiredWorkspaceHorizontalMarginPx;
+            int paddingSide = inv.isFixedLandscape ? 0 : desiredWorkspaceHorizontalMarginPx;
 
             padding.set(paddingSide, paddingTop, paddingSide, paddingBottom);
         }
@@ -1934,7 +1936,7 @@
                 hotseatBarPadding.set(mHotseatBarWorkspaceSpacePx, paddingTop,
                         mInsets.right + mHotseatBarEdgePaddingPx, paddingBottom);
             }
-        } else if (isTaskbarPresent || inv.isFixedLandscapeMode) {
+        } else if (isTaskbarPresent || inv.isFixedLandscape) {
             // Center the QSB vertically with hotseat
             int hotseatBarBottomPadding = getHotseatBarBottomPadding();
             int hotseatBarTopPadding =
@@ -1953,7 +1955,7 @@
             }
             startSpacing += getAdditionalQsbSpace();
 
-            if (inv.isFixedLandscapeMode) {
+            if (inv.isFixedLandscape) {
                 endSpacing += mInsets.right;
                 startSpacing +=  mInsets.left;
             }
@@ -2568,7 +2570,7 @@
             }
             if (mTransposeLayoutWithOrientation == null) {
                 mTransposeLayoutWithOrientation =
-                        !(mInfo.isTablet(mWindowBounds) || mInv.isFixedLandscapeMode);
+                        !(mInfo.isTablet(mWindowBounds) || mInv.isFixedLandscape);
             }
             if (mIsGestureMode == null) {
                 mIsGestureMode = mInfo.getNavigationMode().hasGestures;
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 6be8098..1c18179 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -36,6 +36,8 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.ShortcutAndWidgetContainer.TranslationProvider;
+import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.util.HorizontalInsettableView;
 import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
@@ -201,13 +203,16 @@
         AnimatorSet animatorSet = new AnimatorSet();
         for (int i = 0; i < icons.getChildCount(); i++) {
             View child = icons.getChildAt(i);
-            float tx = shouldAdjustHotseat ? dp.getHotseatAdjustedTranslation(getContext(), i) : 0;
-            if (child instanceof Reorderable) {
-                MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
-                animatorSet.play(
-                        mtd.getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM).animateToValue(tx));
-            } else {
-                animatorSet.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, tx));
+            if (child.getLayoutParams() instanceof CellLayoutLayoutParams lp) {
+                float tx = shouldAdjustHotseat
+                        ? dp.getHotseatAdjustedTranslation(getContext(), lp.getCellX()) : 0;
+                if (child instanceof Reorderable) {
+                    MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
+                    animatorSet.play(
+                            mtd.getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM).animateToValue(tx));
+                } else {
+                    animatorSet.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, tx));
+                }
             }
         }
         //TODO(b/381109832) refactor & simplify adjustment logic
@@ -229,6 +234,13 @@
     }
 
     @Override
+    protected int getTranslationXForCell(int cellX, int cellY) {
+        TranslationProvider translationProvider = getShortcutsAndWidgets().getTranslationProvider();
+        if (translationProvider == null) return 0;
+        return (int) translationProvider.getTranslationX(cellX);
+    }
+
+    @Override
     public void setInsets(Rect insets) {
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
         DeviceProfile grid = mActivity.getDeviceProfile();
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index c044c52..dddc43f 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherPrefs.DB_FILE;
 import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
 import static com.android.launcher3.LauncherPrefs.GRID_NAME;
 import static com.android.launcher3.Utilities.dpiFromPx;
@@ -53,6 +54,7 @@
 
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.DotRenderer;
+import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.DeviceGridState;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.testing.shared.ResourceUtils;
@@ -63,6 +65,7 @@
 import com.android.launcher3.util.ResourceHelper;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.util.window.CachedDisplayInfo;
 import com.android.launcher3.util.window.WindowManagerProxy;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -85,6 +88,8 @@
     public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
             new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
 
+    public static final String GRID_NAME_PREFS_KEY = "idp_grid_name";
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET})
     public @interface DeviceType {
@@ -188,7 +193,7 @@
     @XmlRes
     public int workspaceSpecsId = INVALID_RESOURCE_HANDLE;
     @XmlRes
-    public int rowCountSpecsId = INVALID_RESOURCE_HANDLE;;
+    public int gridSizeSpecsId = INVALID_RESOURCE_HANDLE;;
     @XmlRes
     public int workspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
     @XmlRes
@@ -216,7 +221,7 @@
     /**
      * Fixed landscape mode is the landscape on the phones.
      */
-    public boolean isFixedLandscapeMode = false;
+    public boolean isFixedLandscape = false;
     private LauncherPrefChangeListener mLandscapeModePreferenceListener;
 
     public String dbFile;
@@ -240,11 +245,11 @@
     @TargetApi(23)
     private InvariantDeviceProfile(Context context) {
         String gridName = getCurrentGridName(context);
-        String newGridName = initGrid(context, gridName);
-        if (!newGridName.equals(gridName)) {
-            LauncherPrefs.get(context).put(GRID_NAME, newGridName);
-        }
-
+        FileLog.d(TAG, "New InvariantDeviceProfile, before initGrid(): "
+                + "gridName:" + gridName
+                + ", LauncherPrefs GRID_NAME:" + LauncherPrefs.get(context).get(GRID_NAME)
+                + ", LauncherPrefs DB_FILE:" + LauncherPrefs.get(context).get(DB_FILE));
+        initGrid(context, gridName);
         DisplayController.INSTANCE.get(context).setPriorityListener(
                 (displayContext, info, flags) -> {
                     if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
@@ -255,9 +260,12 @@
                 });
         if (Flags.oneGridSpecs()) {
             mLandscapeModePreferenceListener = (String s) -> {
-                boolean newFixedLandscapeValue = FIXED_LANDSCAPE_MODE.get(context);
-                if (isFixedLandscapeMode != newFixedLandscapeValue) {
-                    setFixedLandscape(context, newFixedLandscapeValue);
+                if (isFixedLandscape != FIXED_LANDSCAPE_MODE.get(context)) {
+                    MAIN_EXECUTOR.execute(() -> {
+                        Trace.beginSection("InvariantDeviceProfile#setFixedLandscape");
+                        onConfigChanged(context.getApplicationContext());
+                        Trace.endSection();
+                    });
                 }
             };
             LauncherPrefs.INSTANCE.get(context).addListener(
@@ -295,7 +303,7 @@
                         gridName,
                         defaultInfo,
                         /*allowDisabledGrid=*/false,
-                        isFixedLandscapeMode
+                        FIXED_LANDSCAPE_MODE.get(context)
                 ),
                 defaultDeviceType);
 
@@ -309,7 +317,7 @@
                         gridName,
                         myInfo,
                         /*allowDisabledGrid=*/false,
-                        isFixedLandscapeMode
+                        FIXED_LANDSCAPE_MODE.get(context)
                 ),
                 deviceType);
 
@@ -345,13 +353,7 @@
     }
 
     private String initGrid(Context context, String gridName) {
-        if (!Flags.oneGridSpecs() && (isFixedLandscapeMode || FIXED_LANDSCAPE_MODE.get(context))) {
-            LauncherPrefs.get(context).put(FIXED_LANDSCAPE_MODE, false);
-            isFixedLandscapeMode = false;
-        }
-
         Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
-
         List<DisplayOption> allOptions = getPredefinedDeviceProfiles(
                 context,
                 gridName,
@@ -370,7 +372,16 @@
                                 ? new ArrayList<>(allOptions)
                                 : new ArrayList<>(allOptionsFilteredByColCount),
                         displayInfo.getDeviceType());
+
+        if (!displayOption.grid.name.equals(gridName)) {
+            LauncherPrefs.get(context).put(GRID_NAME, displayOption.grid.name);
+        }
+
         initGrid(context, displayInfo, displayOption);
+        FileLog.d(TAG, "initGrid: "
+                + "gridName:" + gridName
+                + ", LauncherPrefs GRID_NAME:" + LauncherPrefs.get(context).get(GRID_NAME)
+                + ", LauncherPrefs DB_FILE:" + LauncherPrefs.get(context).get(DB_FILE));
         return displayOption.grid.name;
     }
 
@@ -413,7 +424,7 @@
         isScalable = closestProfile.isScalable;
         devicePaddingId = closestProfile.devicePaddingId;
         workspaceSpecsId = closestProfile.mWorkspaceSpecsId;
-        rowCountSpecsId = closestProfile.mRowCountSpecsId;
+        gridSizeSpecsId = closestProfile.mGridSizeSpecsId;
         workspaceSpecsTwoPanelId = closestProfile.mWorkspaceSpecsTwoPanelId;
         allAppsSpecsId = closestProfile.mAllAppsSpecsId;
         allAppsSpecsTwoPanelId = closestProfile.mAllAppsSpecsTwoPanelId;
@@ -473,7 +484,7 @@
         startAlignTaskbar = displayOption.startAlignTaskbar;
 
         // Fixed Landscape mode
-        isFixedLandscapeMode = FIXED_LANDSCAPE_MODE.get(context) && Flags.oneGridSpecs();
+        isFixedLandscape = closestProfile.mIsFixedLandscape;
 
         // If the partner customization apk contains any grid overrides, apply them
         // Supported overrides: numRows, numColumns, iconSize
@@ -543,24 +554,6 @@
         });
     }
 
-    /**
-     * Updates the fixed landscape mode, this triggers a new IDP, reloads the database and triggers
-     * a grid migration.
-     */
-    public void setFixedLandscape(Context context, boolean isFixedLandscape) {
-        this.isFixedLandscapeMode = isFixedLandscape;
-        if (isFixedLandscape) {
-            // When in isFixedLandscape there should only be one default grid to choose from
-            MAIN_EXECUTOR.execute(() -> {
-                Trace.beginSection("InvariantDeviceProfile#setFixedLandscape");
-                onConfigChanged(context.getApplicationContext());
-                Trace.endSection();
-            });
-        } else {
-            setCurrentGrid(context, LauncherPrefs.get(context).get(GRID_NAME));
-        }
-    }
-
     private Object[] toModelState() {
         return new Object[]{
                 numColumns, numRows, numSearchContainerColumns, numDatabaseHotseatIcons,
@@ -651,14 +644,14 @@
     }
 
     /**
-     * Parses through the xml to find GridDimension specs. Then calls findBestRowCount to get the
-     * correct row count for this GridOption.
+     * Parses through the xml to find GridSize specs. Then calls findBestGridSize to get the
+     * correct grid size for this GridOption.
      *
-     * @return the result of {@link #findBestRowCount(List, int, int)}.
+     * @return the result of {@link #findBestGridSize(List, int, int)}.
      */
-    private static GridDimension getRowCount(ResourceHelper resourceHelper, Context context,
+    private static GridSize getGridSize(ResourceHelper resourceHelper, Context context,
             Info displayInfo) {
-        ArrayList<GridDimension> rowCounts = new ArrayList<>();
+        ArrayList<GridSize> gridSizes = new ArrayList<>();
 
         try (XmlResourceParser parser = resourceHelper.getXml()) {
             final int depth = parser.getDepth();
@@ -666,8 +659,8 @@
             while (((type = parser.next()) != XmlPullParser.END_TAG
                     || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
                 if ((type == XmlPullParser.START_TAG)
-                        && "GridDimension".equals(parser.getName())) {
-                    rowCounts.add(new GridDimension(context, Xml.asAttributeSet(parser)));
+                        && "GridSize".equals(parser.getName())) {
+                    gridSizes.add(new GridSize(context, Xml.asAttributeSet(parser)));
                 }
             }
         } catch (IOException | XmlPullParserException e) {
@@ -677,49 +670,38 @@
         // Finds the min width and height in dp for all displays.
         int[] dimens = findMinWidthAndHeightDpForDevice(displayInfo);
 
-        return findBestRowCount(rowCounts, dimens[0], dimens[1]);
+        return findBestGridSize(gridSizes, dimens[0], dimens[1]);
     }
 
     /**
-     * @return the biggest row count that fits the display dimensions spec using GridDimension to
-     * determine that. If no best row count is found, return null.
+     * @return the biggest grid size that fits the display dimensions.
+     * If no best grid size is found, return null.
      */
-    private static GridDimension findBestRowCount(List<GridDimension> list, int minWidthDp,
+    private static GridSize findBestGridSize(List<GridSize> list, int minWidthDp,
             int minHeightDp) {
-        GridDimension selectedRow = null;
-        for (GridDimension item: list) {
+        GridSize selectedGridSize = null;
+        for (GridSize item: list) {
             if (minWidthDp >= item.mMinDeviceWidthDp && minHeightDp >= item.mMinDeviceHeightDp) {
-                if (selectedRow == null || selectedRow.mNumGridDimension < item.mNumGridDimension) {
-                    selectedRow = item;
+                if (selectedGridSize == null
+                        || (selectedGridSize.mNumColumns <= item.mNumColumns
+                        && selectedGridSize.mNumRows <= item.mNumRows)) {
+                    selectedGridSize = item;
                 }
             }
         }
-        return selectedRow;
+        return selectedGridSize;
     }
 
     private static int[] findMinWidthAndHeightDpForDevice(Info displayInfo) {
-        int minWidthPx = Integer.MAX_VALUE;
-        int minHeightPx = Integer.MAX_VALUE;
-        for (WindowBounds bounds : displayInfo.supportedBounds) {
-            boolean isTablet = displayInfo.isTablet(bounds);
-            if (isTablet && displayInfo.getDeviceType() == TYPE_MULTI_DISPLAY) {
-                // For split displays, take half width per page.
-                minWidthPx = Math.min(minWidthPx, bounds.availableSize.x / 2);
-                minHeightPx = Math.min(minHeightPx, bounds.availableSize.y);
-            } else if (!isTablet && bounds.isLandscape()) {
-                // We will use transposed layout in this case.
-                minWidthPx = Math.min(minWidthPx, bounds.availableSize.y);
-                minHeightPx = Math.min(minHeightPx, bounds.availableSize.x);
-            } else {
-                minWidthPx = Math.min(minWidthPx, bounds.availableSize.x);
-                minHeightPx = Math.min(minHeightPx, bounds.availableSize.y);
-            }
+        int minDisplayWidthDp = Integer.MAX_VALUE;
+        int minDisplayHeightDp = Integer.MAX_VALUE;
+        for (CachedDisplayInfo display: displayInfo.getAllDisplays()) {
+            minDisplayWidthDp = Math.min(minDisplayWidthDp,
+                    (int) dpiFromPx(display.size.x, DisplayMetrics.DENSITY_DEVICE_STABLE));
+            minDisplayHeightDp = Math.min(minDisplayHeightDp,
+                    (int) dpiFromPx(display.size.y, DisplayMetrics.DENSITY_DEVICE_STABLE));
         }
-
-        int minWidthDp = (int) dpiFromPx(minWidthPx, DisplayMetrics.DENSITY_DEVICE_STABLE);
-        int minHeightDp = (int) dpiFromPx(minHeightPx, DisplayMetrics.DENSITY_DEVICE_STABLE);
-
-        return new int[]{minWidthDp, minHeightDp};
+        return new int[]{minDisplayWidthDp, minDisplayHeightDp};
     }
 
     /**
@@ -767,7 +749,7 @@
         return parseAllDefinedGridOptions(context, displayInfo)
                 .stream()
                 .filter(go -> go.isEnabled(deviceType))
-                .filter(go -> go.filterByFlag(deviceType, isFixedLandscapeMode))
+                .filter(go -> go.filterByFlag(deviceType, isFixedLandscape))
                 .collect(Collectors.toList());
     }
 
@@ -1037,7 +1019,7 @@
         private final int mWorkspaceCellSpecsTwoPanelId;
         private final int mAllAppsCellSpecsId;
         private final int mAllAppsCellSpecsTwoPanelId;
-        private final int mRowCountSpecsId;
+        private final int mGridSizeSpecsId;
         private final boolean mIsFixedLandscape;
         private final boolean mIsOldGrid;
 
@@ -1048,18 +1030,20 @@
             title = a.getString(R.styleable.GridDisplayOption_title);
             deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory,
                     DEVICE_CATEGORY_ALL);
-            mRowCountSpecsId = a.getResourceId(
-                    R.styleable.GridDisplayOption_rowCountSpecsId, INVALID_RESOURCE_HANDLE);
+            mGridSizeSpecsId = a.getResourceId(
+                    R.styleable.GridDisplayOption_gridSizeSpecsId, INVALID_RESOURCE_HANDLE);
             mIsDualGrid = a.getBoolean(R.styleable.GridDisplayOption_isDualGrid, false);
-            if (mRowCountSpecsId != INVALID_RESOURCE_HANDLE) {
-                ResourceHelper resourceHelper = new ResourceHelper(context, mRowCountSpecsId);
-                GridDimension numR = getRowCount(resourceHelper, context, displayInfo);
-                numRows = numR.mNumGridDimension;
-                dbFile = numR.mDbFile;
-                defaultLayoutId = numR.mDefaultLayoutId;
-                demoModeLayoutId = numR.mDemoModeLayoutId;
+            if (mGridSizeSpecsId != INVALID_RESOURCE_HANDLE) {
+                ResourceHelper resourceHelper = new ResourceHelper(context, mGridSizeSpecsId);
+                GridSize gridSize = getGridSize(resourceHelper, context, displayInfo);
+                numColumns = gridSize.mNumColumns;
+                numRows = gridSize.mNumRows;
+                dbFile = gridSize.mDbFile;
+                defaultLayoutId = gridSize.mDefaultLayoutId;
+                demoModeLayoutId = gridSize.mDemoModeLayoutId;
             } else {
                 numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0);
+                numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
                 dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
                 defaultLayoutId = a.getResourceId(
                         R.styleable.GridDisplayOption_defaultLayoutId, 0);
@@ -1067,7 +1051,6 @@
                         R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId);
             }
 
-            numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
             numSearchContainerColumns = a.getInt(
                     R.styleable.GridDisplayOption_numSearchContainerColumns, numColumns);
 
@@ -1230,7 +1213,7 @@
             }
 
             // Here we return true if we want to show the new grids.
-            if (mRowCountSpecsId != INVALID_RESOURCE_HANDLE) {
+            if (mGridSizeSpecsId != INVALID_RESOURCE_HANDLE) {
                 return Flags.oneGridSpecs();
             }
 
@@ -1243,26 +1226,28 @@
         }
     }
 
-    public static final class GridDimension {
-        final int mNumGridDimension;
-        final int mMinDeviceWidthDp;
-        final int mMinDeviceHeightDp;
+    public static final class GridSize {
+        final int mNumRows;
+        final int mNumColumns;
+        final float mMinDeviceWidthDp;
+        final float mMinDeviceHeightDp;
         final String mDbFile;
         final int mDefaultLayoutId;
         final int mDemoModeLayoutId;
 
 
-        GridDimension(Context context, AttributeSet attrs) {
-            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridDimension);
+        GridSize(Context context, AttributeSet attrs) {
+            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridSize);
 
-            mNumGridDimension = (int) a.getFloat(R.styleable.GridDimension_numGridDimension, 0);
-            mMinDeviceWidthDp = a.getInt(R.styleable.GridDimension_minDeviceWidthDp, 0);
-            mMinDeviceHeightDp = a.getInt(R.styleable.GridDimension_minDeviceHeightDp, 0);
-            mDbFile = a.getString(R.styleable.GridDimension_dbFile);
+            mNumRows = (int) a.getFloat(R.styleable.GridSize_numGridRows, 0);
+            mNumColumns = (int) a.getFloat(R.styleable.GridSize_numGridColumns, 0);
+            mMinDeviceWidthDp = a.getFloat(R.styleable.GridSize_minDeviceWidthDp, 0);
+            mMinDeviceHeightDp = a.getFloat(R.styleable.GridSize_minDeviceHeightDp, 0);
+            mDbFile = a.getString(R.styleable.GridSize_dbFile);
             mDefaultLayoutId = a.getResourceId(
-                    R.styleable.GridDimension_defaultLayoutId, 0);
+                    R.styleable.GridSize_defaultLayoutId, 0);
             mDemoModeLayoutId = a.getResourceId(
-                    R.styleable.GridDimension_demoModeLayoutId, mDefaultLayoutId);
+                    R.styleable.GridSize_demoModeLayoutId, mDefaultLayoutId);
 
             a.recycle();
         }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8981024..cb021c7 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -46,6 +46,7 @@
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_ACTIVITY_RESULT;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_REQUEST_ARGS;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_REQUEST_CODE;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_WIDGET_PANEL;
 import static com.android.launcher3.LauncherConstants.TraceEvents.COLD_STARTUP_TRACE_COOKIE;
 import static com.android.launcher3.LauncherConstants.TraceEvents.COLD_STARTUP_TRACE_METHOD_NAME;
@@ -789,7 +790,7 @@
             LauncherPrefs.get(this).put(LauncherPrefs.ALLOW_ROTATION, false);
         }
         getRotationHelper().setFixedLandscape(
-                Objects.requireNonNull(mDeviceProfile.inv).isFixedLandscapeMode
+                Objects.requireNonNull(mDeviceProfile.inv).isFixedLandscape
         );
     }
 
@@ -1352,7 +1353,8 @@
 
         NonConfigInstance lastInstance = (NonConfigInstance) getLastNonConfigurationInstance();
         boolean forceRestore = lastInstance != null
-                && (lastInstance.config.diff(mOldConfig) & CONFIG_UI_MODE) != 0;
+                && ((lastInstance.config.diff(mOldConfig) & CONFIG_UI_MODE) != 0
+                || savedState.getBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME));
         if (forceRestore || !state.shouldDisableRestore()) {
             mStateManager.goToState(state, false /* animated */);
         }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 01d0a74..a53238d 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -20,8 +20,12 @@
 import static android.content.Context.RECEIVER_EXPORTED;
 
 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
+import static com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY;
+import static com.android.launcher3.LauncherPrefs.DB_FILE;
+import static com.android.launcher3.LauncherPrefs.GRID_NAME;
 import static com.android.launcher3.LauncherPrefs.ICON_STATE;
 import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
+import static com.android.launcher3.model.DeviceGridState.KEY_DB_FILE;
 import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -47,6 +51,7 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.icons.LauncherIconProvider;
 import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.ModelLauncherCallbacks;
 import com.android.launcher3.model.WidgetsFilterDataProvider;
 import com.android.launcher3.notification.NotificationListener;
@@ -70,6 +75,7 @@
 
 public class LauncherAppState implements SafeCloseable {
 
+    public static final String TAG = "LauncherAppState";
     public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
 
     // We do not need any synchronization for this variable as its only written on UI thread.
@@ -293,6 +299,12 @@
             if (Themes.KEY_THEMED_ICONS.equals(key)) {
                 mIconProvider.setIconThemeSupported(Themes.isThemedIconEnabled(mContext));
                 verifyIconChanged();
+            } else if (GRID_NAME_PREFS_KEY.equals(key)) {
+                FileLog.d(TAG, "onPrefChanged GRID_NAME changed: "
+                        + LauncherPrefs.get(mContext).get(GRID_NAME));
+            } else if (KEY_DB_FILE.equals(key)) {
+                FileLog.d(TAG, "onPrefChanged DB_FILE changed: "
+                        + LauncherPrefs.get(mContext).get(DB_FILE));
             }
         }
     }
diff --git a/src/com/android/launcher3/LauncherConstants.java b/src/com/android/launcher3/LauncherConstants.java
index 445fb41..5cba82f 100644
--- a/src/com/android/launcher3/LauncherConstants.java
+++ b/src/com/android/launcher3/LauncherConstants.java
@@ -67,5 +67,8 @@
         static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
         // Type int[]
         static final String RUNTIME_STATE_CURRENT_SCREEN_IDS = "launcher.current_screen_ids";
+        // Type: boolean
+        public static final String RUNTIME_STATE_RECREATE_TO_UPDATE_THEME =
+                "launcher.recreate_to_update_theme";
     }
 }
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index a5b8168..c702414 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -24,6 +24,7 @@
     public static final String LAUNCHER_4_BY_4_DB = "launcher_4_by_4.db";
     public static final String LAUNCHER_3_BY_3_DB = "launcher_3_by_3.db";
     public static final String LAUNCHER_2_BY_2_DB = "launcher_2_by_2.db";
+    public static final String LAUNCHER_7_BY_3_DB = "launcher_7_by_3.db";
     public static final String LAUNCHER_8_BY_3_DB = "launcher_8_by_3.db";
     public static final String BACKUP_DB = "backup.db";
     public static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
@@ -45,6 +46,7 @@
             LAUNCHER_4_BY_4_DB,
             LAUNCHER_3_BY_3_DB,
             LAUNCHER_2_BY_2_DB,
+            LAUNCHER_7_BY_3_DB,
             LAUNCHER_8_BY_3_DB));
 
     public static final List<String> OTHER_FILES = Collections.unmodifiableList(Arrays.asList(
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 712c56c..5b9c2fa 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -20,6 +20,7 @@
 import android.content.SharedPreferences
 import androidx.annotation.VisibleForTesting
 import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
+import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY
 import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
 import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
 import com.android.launcher3.model.DeviceGridState
@@ -138,7 +139,7 @@
         @JvmField
         val GRID_NAME =
             ConstantItem(
-                "idp_grid_name",
+                GRID_NAME_PREFS_KEY,
                 isBackedUp = true,
                 defaultValue = null,
                 encryptionType = EncryptionType.ENCRYPTED,
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index d645734..a5b95c7 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -10,7 +10,6 @@
 import android.view.WindowInsets;
 
 import com.android.launcher3.graphics.SysUiScrim;
-import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.statemanager.StatefulContainer;
 import com.android.launcher3.util.window.WindowManagerProxy;
 
@@ -56,7 +55,10 @@
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         mStatefulContainer.handleConfigurationChanged(
                 mStatefulContainer.getContext().getResources().getConfiguration());
+        return updateInsets(insets);
+    }
 
+    private WindowInsets updateInsets(WindowInsets insets) {
         insets = WindowManagerProxy.INSTANCE.get(getContext())
                 .normalizeWindowInsets(getContext(), insets, mTempRect);
         handleSystemWindowInsets(mTempRect);
@@ -74,7 +76,11 @@
     }
 
     public void dispatchInsets() {
-        mStatefulContainer.getDeviceProfile().updateInsets(mInsets);
+        if (isAttachedToWindow()) {
+            updateInsets(getRootWindowInsets());
+        } else {
+            mStatefulContainer.getDeviceProfile().updateInsets(mInsets);
+        }
         super.setInsets(mInsets);
     }
 
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index a8733f2..180ed87 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -330,8 +330,13 @@
         mTranslationProvider = provider;
     }
 
+    /** Returns the current {@link TranslationProvider translation provider}. */
+    public @Nullable TranslationProvider getTranslationProvider() {
+        return mTranslationProvider;
+    }
+
     /** Provides translation values to apply when laying out child views. */
-    interface TranslationProvider {
+    public interface TranslationProvider {
         float getTranslationX(int cellX);
     }
 }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 71a2589..ebcb5da 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
-
 import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
 import static com.android.launcher3.Flags.enableSmartspaceAsAWidget;
 import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
@@ -36,9 +34,6 @@
 import android.content.pm.ShortcutInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BlendMode;
-import android.graphics.BlendModeColorFilter;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.LightingColorFilter;
@@ -49,10 +44,8 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.os.DeadObjectException;
@@ -85,7 +78,6 @@
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.CacheableShortcutInfo;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.icons.ThemedIconDrawable;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
@@ -696,29 +688,19 @@
             return null;
         }
 
-        // Inject monochrome icon drawable
+        // Inject theme icon drawable
         if (ATLEAST_T && useTheme) {
-            result.mutate();
-            int[] colors = ThemedIconDrawable.getColors(context);
-            Drawable mono = result.getMonochrome();
-
-            if (mono != null) {
-                mono.setTint(colors[1]);
-            } else  if (info instanceof ItemInfoWithIcon iiwi) {
-                // Inject a previously generated monochrome icon
-                Bitmap monoBitmap = iiwi.bitmap.getMono();
-                if (monoBitmap != null) {
-                    // Use BitmapDrawable instead of FastBitmapDrawable so that the colorState is
-                    // preserved in constantState
-                    mono = new BitmapDrawable(monoBitmap);
-                    mono.setColorFilter(new BlendModeColorFilter(colors[1], BlendMode.SRC_IN));
-                    // Inset the drawable according to the AdaptiveIconDrawable layers
-                    mono = new InsetDrawable(mono, getExtraInsetFraction() / 2);
+            try (LauncherIcons li = LauncherIcons.obtain(context)) {
+                if (li.getThemeController() != null) {
+                    AdaptiveIconDrawable themed = li.getThemeController().createThemedAdaptiveIcon(
+                            context,
+                            result,
+                            info instanceof ItemInfoWithIcon iiwi ? iiwi.bitmap : null);
+                    if (themed != null) {
+                        result = themed;
+                    }
                 }
             }
-            if (mono != null) {
-                result = new AdaptiveIconDrawable(new ColorDrawable(colors[0]), mono);
-            }
         }
 
         if (badge == null) {
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 814d142..68a6e62 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -48,6 +48,8 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.shortcuts.DeepShortcutTextView;
+import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
@@ -104,11 +106,15 @@
                 R.string.action_deep_shortcut, KeyEvent.KEYCODE_S));
     }
 
+    private static boolean isNotInShortcutMenu(@Nullable View view) {
+        return view == null || !(view.getParent() instanceof DeepShortcutView);
+    }
+
     @Override
     protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) {
         // If the request came from keyboard, do not add custom shortcuts as that is already
         // exposed as a direct shortcut
-        if (ShortcutUtil.supportsShortcuts(item)) {
+        if (isNotInShortcutMenu(host) && ShortcutUtil.supportsShortcuts(item)) {
             out.add(mActions.get(DEEP_SHORTCUTS));
         }
 
@@ -415,7 +421,6 @@
                         screenId, coordinates[0], coordinates[1]);
 
                 bindItem(info, accessibility, finishCallback);
-                announceConfirmation(R.string.item_added_to_workspace);
             } else if (item instanceof PendingAddItemInfo) {
                 PendingAddItemInfo info = (PendingAddItemInfo) item;
                 if (info instanceof PendingAddWidgetInfo widgetInfo
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 8505a6d..b0001af 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -761,6 +761,22 @@
         }
     }
 
+    @Override
+    public void addChildrenForAccessibility(ArrayList<View> arrayList) {
+        super.addChildrenForAccessibility(arrayList);
+        if (!Flags.floatingSearchBar()) {
+            // Searchbox container is visually at the top of the all apps UI but it's present in
+            // end of the children list.
+            // We need to move the searchbox to the top in a11y tree for a11y services to read the
+            // all apps screen in same as visual order.
+            arrayList.stream().filter(v -> v.getId() == R.id.search_container_all_apps)
+                    .findFirst().ifPresent(v -> {
+                        arrayList.remove(v);
+                        arrayList.add(0, v);
+                    });
+        }
+    }
+
     protected void updateHeaderScroll(int scrolledOffset) {
         float prog1 = Utilities.boundToRange((float) scrolledOffset / mHeaderThreshold, 0f, 1f);
         int headerColor = getHeaderColor(prog1);
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index df383bf..b6ba264 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -125,6 +125,12 @@
         }
 
         @Override
+        public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
+                RecyclerView.State state) {
+            return mAppsPerRow;
+        }
+
+        @Override
         public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
                 RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
             super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info);
diff --git a/src/com/android/launcher3/allapps/WorkPausedCard.java b/src/com/android/launcher3/allapps/WorkPausedCard.java
index e1eeabe..a14ac98 100644
--- a/src/com/android/launcher3/allapps/WorkPausedCard.java
+++ b/src/com/android/launcher3/allapps/WorkPausedCard.java
@@ -79,7 +79,6 @@
 
     @Override
     public void onClick(View view) {
-        setEnabled(false);
         mActivityContext.getAppsView().getWorkManager().setWorkProfileEnabled(true);
         mActivityContext.getStatsLogManager().logger().log(LAUNCHER_TURN_ON_WORK_APPS_TAP);
     }
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index 6ebab5a..6d7d193 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -148,7 +148,7 @@
         if (getAH() != null) {
             getAH().applyPadding();
         }
-        mWorkUtilityView.setOnClickListener(this::onWorkFabClicked);
+        mWorkUtilityView.getWorkFAB().setOnClickListener(this::onWorkFabClicked);
         return true;
     }
     /**
diff --git a/src/com/android/launcher3/allapps/WorkUtilityView.java b/src/com/android/launcher3/allapps/WorkUtilityView.java
index 4b58ab0..b263639 100644
--- a/src/com/android/launcher3/allapps/WorkUtilityView.java
+++ b/src/com/android/launcher3/allapps/WorkUtilityView.java
@@ -25,6 +25,7 @@
 import android.graphics.Rect;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
 import android.widget.ImageButton;
@@ -33,6 +34,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 import androidx.core.graphics.Insets;
 import androidx.core.view.WindowInsetsCompat;
 
@@ -82,6 +84,7 @@
     // Threshold when user scrolls up/down to determine when should button extend/collapse
     private final int mScrollThreshold;
     private ValueAnimator mPauseFABAnim;
+    private View mWorkFAB;
     private TextView mPauseText;
     private ImageView mWorkIcon;
     private ImageButton mSchedulerButton;
@@ -116,6 +119,7 @@
 
         mPauseText = findViewById(R.id.pause_text);
         mWorkIcon = findViewById(R.id.work_icon);
+        mWorkFAB = findViewById(R.id.work_mode_toggle);
         mSchedulerButton = findViewById(R.id.work_scheduler);
         setSelected(true);
         KeyboardInsetAnimationCallback keyboardInsetAnimationCallback =
@@ -126,6 +130,7 @@
         setInsets(mActivityContext.getDeviceProfile().getInsets());
         updateStringFromCache();
         mSchedulerButton.setVisibility(GONE);
+        mSchedulerButton.setOnClickListener(null);
         if (shouldUseScheduler()) {
             mSchedulerButton.setVisibility(VISIBLE);
             mSchedulerButton.setOnClickListener(view ->
@@ -386,6 +391,10 @@
         return mScrollThreshold;
     }
 
+    public View getWorkFAB() {
+        return mWorkFAB;
+    }
+
     public void updateStringFromCache(){
         StringCache cache = mActivityContext.getStringCache();
         if (cache != null) {
@@ -393,7 +402,13 @@
         }
     }
 
-    private boolean shouldUseScheduler() {
+    @VisibleForTesting
+    boolean shouldUseScheduler() {
         return Flags.workSchedulerInWorkProfile() && !mWorkSchedulerIntentAction.isEmpty();
     }
+
+    @VisibleForTesting
+    ImageButton getSchedulerButton() {
+        return mSchedulerButton;
+    }
 }
diff --git a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
index b05539a..1502811 100644
--- a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
+++ b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
@@ -16,11 +16,16 @@
     @Retention(AnnotationRetention.SOURCE)
     @StringDef(
         RestoreError.PROFILE_DELETED,
-        RestoreError.MISSING_INFO,
         RestoreError.MISSING_WIDGET_PROVIDER,
-        RestoreError.INVALID_LOCATION,
+        RestoreError.OVERLAPPING_ITEM,
+        RestoreError.INVALID_WIDGET_SIZE,
+        RestoreError.INVALID_WIDGET_CONTAINER,
         RestoreError.SHORTCUT_NOT_FOUND,
-        RestoreError.APP_NOT_INSTALLED,
+        RestoreError.APP_NO_TARGET_PACKAGE,
+        RestoreError.APP_NO_DB_INTENT,
+        RestoreError.APP_NO_LAUNCH_INTENT,
+        RestoreError.APP_NOT_RESTORED_OR_INSTALLING,
+        RestoreError.APP_NOT_INSTALLED_EXTERNAL_MEDIA,
         RestoreError.WIDGETS_DISABLED,
         RestoreError.PROFILE_NOT_RESTORED,
         RestoreError.WIDGET_REMOVED,
@@ -28,15 +33,24 @@
         RestoreError.GRID_MIGRATION_FAILURE,
         RestoreError.NO_SEARCH_WIDGET,
         RestoreError.INVALID_WIDGET_ID,
+        RestoreError.OTHER_WIDGET_INFLATION_FAIL,
+        RestoreError.UNSPECIFIED_WIDGET_INFLATION_RESULT,
+        RestoreError.UNRESTORED_PENDING_WIDGET,
+        RestoreError.INVALID_CUSTOM_WIDGET_ID,
     )
     annotation class RestoreError {
         companion object {
             const val PROFILE_DELETED = "user_profile_deleted"
-            const val MISSING_INFO = "missing_information_when_loading"
             const val MISSING_WIDGET_PROVIDER = "missing_widget_provider"
-            const val INVALID_LOCATION = "invalid_size_or_location"
+            const val OVERLAPPING_ITEM = "overlapping_item"
+            const val INVALID_WIDGET_SIZE = "invalid_widget_size"
+            const val INVALID_WIDGET_CONTAINER = "invalid_widget_container"
             const val SHORTCUT_NOT_FOUND = "shortcut_not_found"
-            const val APP_NOT_INSTALLED = "app_not_installed"
+            const val APP_NO_TARGET_PACKAGE = "app_no_target_package"
+            const val APP_NO_DB_INTENT = "app_no_db_intent"
+            const val APP_NO_LAUNCH_INTENT = "app_no_launch_intent"
+            const val APP_NOT_RESTORED_OR_INSTALLING = "app_not_restored_or_installed"
+            const val APP_NOT_INSTALLED_EXTERNAL_MEDIA = "app_not_installed_external_media"
             const val WIDGETS_DISABLED = "widgets_disabled"
             const val PROFILE_NOT_RESTORED = "profile_not_restored"
             const val DATABASE_FILE_NOT_RESTORED = "db_file_not_restored"
@@ -44,6 +58,10 @@
             const val GRID_MIGRATION_FAILURE = "grid_migration_failed"
             const val NO_SEARCH_WIDGET = "no_search_widget"
             const val INVALID_WIDGET_ID = "invalid_widget_id"
+            const val OTHER_WIDGET_INFLATION_FAIL = "other_widget_fail"
+            const val UNSPECIFIED_WIDGET_INFLATION_RESULT = "unspecified_widget_inflation_result"
+            const val UNRESTORED_PENDING_WIDGET = "unrestored_pending_widget"
+            const val INVALID_CUSTOM_WIDGET_ID = "invalid_custom_widget_id"
         }
     }
 
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index fb486f7..1e3df1e 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -24,6 +24,7 @@
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PluginManagerWrapper;
@@ -48,6 +49,7 @@
     ApiWrapper getApiWrapper();
     ContextualEduStatsManager getContextualEduStatsManager();
     CustomWidgetManager getCustomWidgetManager();
+    DynamicResource getDynamicResource();
     IconShape getIconShape();
     InstallSessionHelper getInstallSessionHelper();
     ItemInstallQueue getItemInstallQueue();
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index a24f3ff..a826b9e 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -69,7 +69,9 @@
 public class DragLayer extends BaseDragLayer<Launcher> implements LauncherOverlayCallbacks {
 
     public static final int ALPHA_INDEX_OVERLAY = 0;
-    private static final int ALPHA_CHANNEL_COUNT = 1;
+
+    public static final int ALPHA_INDEX_LOADER = 1;
+    private static final int ALPHA_CHANNEL_COUNT = 2;
 
     public static final int ANIMATION_END_DISAPPEAR = 0;
     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index bcee442..67fe889 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -224,7 +224,6 @@
         measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
 
         mBlurSizeOutline = getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
-        setElevation(getResources().getDimension(R.dimen.drag_elevation));
         setWillNotDraw(false);
     }
 
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
deleted file mode 100644
index bebef70..0000000
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2010 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.dragndrop;
-
-import com.android.launcher3.Alarm;
-import com.android.launcher3.CellLayout;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.OnAlarmListener;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
-
-public class SpringLoadedDragController implements OnAlarmListener {
-    // how long the user must hover over a mini-screen before it unshrinks
-    private static final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
-    private static final long ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST = 3000;
-    private static final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
-
-    Alarm mAlarm;
-
-    // the screen the user is currently hovering over, if any
-    private CellLayout mScreen;
-    private Launcher mLauncher;
-
-    public SpringLoadedDragController(Launcher launcher) {
-        mLauncher = launcher;
-        mAlarm = new Alarm();
-        mAlarm.setOnAlarmListener(this);
-    }
-
-    private long getEnterSpringLoadHoverTime() {
-        // Some TAPL tests are flaky on Cuttlefish with a low waiting time
-        return Utilities.isRunningInTestHarness()
-                ? ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST
-                : ENTER_SPRING_LOAD_HOVER_TIME;
-    }
-
-    public void cancel() {
-        mAlarm.cancelAlarm();
-    }
-
-    // Set a new alarm to expire for the screen that we are hovering over now
-    public void setAlarm(CellLayout cl) {
-        mAlarm.cancelAlarm();
-        mAlarm.setAlarm((cl == null) ? ENTER_SPRING_LOAD_CANCEL_HOVER_TIME
-                : getEnterSpringLoadHoverTime());
-        mScreen = cl;
-    }
-
-    // this is called when our timer runs out
-    public void onAlarm(Alarm alarm) {
-        if (mScreen != null) {
-            // Snap to the screen that we are hovering over now
-            Workspace<?> w = mLauncher.getWorkspace();
-            if (!w.isVisible(mScreen)) {
-                w.snapToPage(w.indexOfChild(mScreen));
-            }
-        } else {
-            mLauncher.getDragController().cancelDrag();
-        }
-    }
-}
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.kt b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.kt
new file mode 100644
index 0000000..2aeab9d
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 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.dragndrop
+
+import com.android.launcher3.Alarm
+import com.android.launcher3.CellLayout
+import com.android.launcher3.Launcher
+import com.android.launcher3.OnAlarmListener
+import com.android.launcher3.Utilities
+
+class SpringLoadedDragController(private val launcher: Launcher) : OnAlarmListener {
+    internal val alarm = Alarm().also { it.setOnAlarmListener(this) }
+
+    // the screen the user is currently hovering over, if any
+    private var screen: CellLayout? = null
+
+    fun cancel() = alarm.cancelAlarm()
+
+    // Set a new alarm to expire for the screen that we are hovering over now
+    fun setAlarm(cl: CellLayout?) {
+        cancel()
+        alarm.setAlarm(
+            when {
+                cl == null -> ENTER_SPRING_LOAD_CANCEL_HOVER_TIME
+                // Some TAPL tests are flaky on Cuttlefish with a low waiting time
+                Utilities.isRunningInTestHarness() -> ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST
+                else -> ENTER_SPRING_LOAD_HOVER_TIME
+            }
+        )
+        screen = cl
+    }
+
+    // this is called when our timer runs out
+    override fun onAlarm(alarm: Alarm) {
+        if (screen != null) {
+            // Snap to the screen that we are hovering over now
+            with(launcher.workspace) { if (!isVisible(screen)) snapToPage(indexOfChild(screen)) }
+        } else {
+            launcher.dragController.cancelDrag()
+        }
+    }
+
+    companion object {
+        // how long the user must hover over a mini-screen before it unshrinks
+        private const val ENTER_SPRING_LOAD_HOVER_TIME: Long = 500
+        private const val ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST: Long = 3000
+        private const val ENTER_SPRING_LOAD_CANCEL_HOVER_TIME: Long = 950
+    }
+}
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 884d448..839dfb7 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -17,15 +17,13 @@
 package com.android.launcher3.icons;
 
 import android.content.Context;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.icons.mono.MonoIconThemeController;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SafeCloseable;
@@ -57,13 +55,13 @@
 
     private final ConcurrentLinkedQueue<LauncherIcons> mPool;
 
-    private MonochromeIconFactory mMonochromeIconFactory;
-
     protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize,
             ConcurrentLinkedQueue<LauncherIcons> pool) {
         super(context, fillResIconDpi, iconBitmapSize,
                 IconShape.INSTANCE.get(context).getShape().enableShapeDetection());
-        mMonoIconEnabled = Themes.isThemedIconEnabled(context);
+        if (Themes.isThemedIconEnabled(context)) {
+            mThemeController = new MonoIconThemeController();
+        }
         mPool = pool;
     }
 
@@ -75,18 +73,6 @@
         mPool.add(this);
     }
 
-    @Override
-    protected Drawable getMonochromeDrawable(AdaptiveIconDrawable base) {
-        Drawable mono = super.getMonochromeDrawable(base);
-        if (mono != null || !Flags.forceMonochromeAppIcons()) {
-            return mono;
-        }
-        if (mMonochromeIconFactory == null) {
-            mMonochromeIconFactory = new MonochromeIconFactory(mIconBitmapSize);
-        }
-        return mMonochromeIconFactory.wrap(base);
-    }
-
     @NonNull
     @Override
     protected UserIconInfo getUserInfo(@NonNull UserHandle user) {
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index dbab52a..6eb02ab 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -301,6 +301,12 @@
         @UiEvent(doc = "User swipes or fling in RIGHT direction on the bottom bazel area.")
         LAUNCHER_QUICKSWITCH_RIGHT(572),
 
+        @UiEvent(doc = "User swipes or fling on the bottom bazel area to enter Desktop mode.")
+        LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE(2025),
+
+        @UiEvent(doc = "User swipes or fling on the bottom bazel area to exit Desktop mode.")
+        LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE(2026),
+
         @UiEvent(doc = "User swipes or fling in DOWN direction on the bottom bazel area.")
         LAUNCHER_SWIPEDOWN_NAVBAR(573),
 
diff --git a/src/com/android/launcher3/model/DbEntry.kt b/src/com/android/launcher3/model/DbEntry.kt
index b79d312..da9779e 100644
--- a/src/com/android/launcher3/model/DbEntry.kt
+++ b/src/com/android/launcher3/model/DbEntry.kt
@@ -30,7 +30,6 @@
 import com.android.launcher3.LauncherSettings.Favorites.SPANY
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.util.ContentWriter
-import java.net.URISyntaxException
 import java.util.Objects
 
 class DbEntry : ItemInfo(), Comparable<DbEntry> {
@@ -129,8 +128,8 @@
     private fun cleanIntentString(intentStr: String): String {
         try {
             return Intent.parseUri(intentStr, 0).apply { sourceBounds = null }.toURI()
-        } catch (e: URISyntaxException) {
-            Log.e(TAG, "Unable to parse Intent string", e)
+        } catch (e: Exception) {
+            Log.e(TAG, "Unable to parse Intent string: $intentStr", e)
             return intentStr
         }
     }
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 84130c7..c01b1b6 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -498,7 +498,7 @@
                 mRestoreEventLogger.logSingleFavoritesItemRestored(itemType);
             }
         } else {
-            markDeleted("Item position overlap", RestoreError.INVALID_LOCATION);
+            markDeleted("Item position overlap", RestoreError.OVERLAPPING_ITEM);
         }
     }
 
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 83eace8..f96e959 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -389,7 +389,7 @@
             }
         } catch (CancellationException e) {
             // Loader stopped, ignore
-            logASplit("Cancelled");
+            FileLog.w(TAG, "LoaderTask cancelled:", e);
         } catch (Exception e) {
             memoryLogger.printLogs();
             throw e;
@@ -398,6 +398,7 @@
     }
 
     public synchronized void stopLocked() {
+        FileLog.w(TAG, "LoaderTask#stopLocked:", new Exception());
         mStopped = true;
         this.notify();
     }
diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index e757a68..0caeb8d 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -1,19 +1,8 @@
 package com.android.launcher3.model;
 
-import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
-import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
-import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX;
-
-import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
-import android.content.res.Resources;
-import android.util.SparseArray;
-import android.widget.RemoteViews;
 
-import androidx.core.os.BuildCompat;
-
-import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.BitmapInfo;
@@ -21,7 +10,6 @@
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.widget.WidgetManagerHelper;
 
 /**
  * An wrapper over various items displayed in a widget picker,
@@ -37,11 +25,9 @@
     public final String label;
     public final CharSequence description;
     public final int spanX, spanY;
-    public final SparseArray<RemoteViews> generatedPreviews;
 
     public WidgetItem(LauncherAppWidgetProviderInfo info,
-            InvariantDeviceProfile idp, IconCache iconCache, Context context,
-            WidgetManagerHelper helper) {
+            InvariantDeviceProfile idp, IconCache iconCache, Context context) {
         super(info.provider, info.getProfile());
 
         label = iconCache.getTitleNoCache(info);
@@ -51,27 +37,6 @@
 
         spanX = Math.min(info.spanX, idp.numColumns);
         spanY = Math.min(info.spanY, idp.numRows);
-
-        if (BuildCompat.isAtLeastV() && Flags.enableGeneratedPreviews()) {
-            generatedPreviews = new SparseArray<>(3);
-            for (int widgetCategory : new int[] {
-                    WIDGET_CATEGORY_HOME_SCREEN,
-                    WIDGET_CATEGORY_KEYGUARD,
-                    WIDGET_CATEGORY_SEARCHBOX,
-            }) {
-                if ((widgetCategory & widgetInfo.generatedPreviewCategories) != 0) {
-                    generatedPreviews.put(widgetCategory,
-                            helper.loadGeneratedPreview(widgetInfo, widgetCategory));
-                }
-            }
-        } else {
-            generatedPreviews = null;
-        }
-    }
-
-    public WidgetItem(LauncherAppWidgetProviderInfo info,
-            InvariantDeviceProfile idp, IconCache iconCache, Context context) {
-        this(info, idp, iconCache, context, new WidgetManagerHelper(context));
     }
 
     public WidgetItem(ShortcutConfigActivityInfo info, IconCache iconCache) {
@@ -82,7 +47,6 @@
         widgetInfo = null;
         activityInfo = info;
         spanX = spanY = 1;
-        generatedPreviews = null;
     }
 
     /**
@@ -101,26 +65,8 @@
         return false;
     }
 
-    /** Returns whether this {@link WidgetItem} has a preview layout that can be used. */
-    @SuppressLint("NewApi") // Already added API check.
-    public boolean hasPreviewLayout() {
-        return widgetInfo != null && widgetInfo.previewLayout != Resources.ID_NULL;
-    }
-
     /** Returns whether this {@link WidgetItem} is for a shortcut rather than an app widget. */
     public boolean isShortcut() {
         return activityInfo != null;
     }
-
-    /**
-     * Returns whether this {@link WidgetItem} has a generated preview for the given widget
-     * category.
-     */
-    public boolean hasGeneratedPreview(int widgetCategory) {
-        if (!Flags.enableGeneratedPreviews() || generatedPreviews == null) {
-            return false;
-        }
-        return generatedPreviews.contains(widgetCategory)
-                && generatedPreviews.get(widgetCategory) != null;
-    }
 }
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 01d4996..a27d2f1 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -149,8 +149,7 @@
                         LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo);
 
                 widgetsAndShortcuts.add(new WidgetItem(
-                        launcherWidgetInfo, idp, app.getIconCache(), app.getContext(),
-                        widgetManager));
+                        launcherWidgetInfo, idp, app.getIconCache(), app.getContext()));
                 updatedItems.add(launcherWidgetInfo);
             }
 
@@ -213,7 +212,6 @@
         if (!WIDGETS_ENABLED) {
             return;
         }
-        WidgetManagerHelper widgetManager = new WidgetManagerHelper(app.getContext());
         for (Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsByPackageItem.entrySet()) {
             if (packageNames.contains(entry.getKey().packageName)) {
                 List<WidgetItem> items = entry.getValue();
@@ -226,7 +224,7 @@
                         } else {
                             items.set(i, new WidgetItem(item.widgetInfo,
                                     app.getInvariantDeviceProfile(), app.getIconCache(),
-                                    app.getContext(), widgetManager));
+                                    app.getContext()));
                         }
                     }
                 }
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index c02336e..e86b592 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -141,7 +141,7 @@
         var allowMissingTarget = false
         var intent = c.parseIntent()
         if (intent == null) {
-            c.markDeleted("Null intent from db for item id=${c.id}", RestoreError.MISSING_INFO)
+            c.markDeleted("Null intent from db for item id=${c.id}", RestoreError.APP_NO_DB_INTENT)
             return
         }
         var disabledState =
@@ -151,7 +151,10 @@
         val cn = intent.component
         val targetPkg = cn?.packageName ?: intent.getPackage()
         if (targetPkg.isNullOrEmpty()) {
-            c.markDeleted("No target package for item id=${c.id}", RestoreError.MISSING_INFO)
+            c.markDeleted(
+                "No target package for item id=${c.id}",
+                RestoreError.APP_NO_TARGET_PACKAGE,
+            )
             return
         }
         val appInfoWrapper = ApplicationInfoWrapper(app.context, targetPkg, c.user)
@@ -180,7 +183,7 @@
                     c.markDeleted(
                         "No Activities found for id=${c.id}, targetPkg=$targetPkg, component=$cn." +
                             " Unable to create launch Intent.",
-                        RestoreError.MISSING_INFO,
+                        RestoreError.APP_NO_LAUNCH_INTENT,
                     )
                     return
                 }
@@ -215,7 +218,7 @@
                             else -> {
                                 c.markDeleted(
                                     "removing app that is not restored and not installing. package: $targetPkg",
-                                    RestoreError.APP_NOT_INSTALLED,
+                                    RestoreError.APP_NOT_RESTORED_OR_INSTALLING,
                                 )
                                 return
                             }
@@ -240,7 +243,7 @@
                         // Do not wait for external media load anymore.
                         c.markDeleted(
                             "Invalid package removed: $targetPkg",
-                            RestoreError.APP_NOT_INSTALLED,
+                            RestoreError.APP_NOT_INSTALLED_EXTERNAL_MEDIA,
                         )
                         return
                     }
@@ -448,7 +451,7 @@
                     ", id=${c.id}," +
                     ", appWidgetId=${c.appWidgetId}," +
                     ", component=${component}",
-                RestoreError.INVALID_LOCATION,
+                RestoreError.INVALID_WIDGET_SIZE,
             )
             return
         }
@@ -459,7 +462,7 @@
                     ", appWidgetId=${c.appWidgetId}," +
                     ", component=${component}," +
                     ", container=${c.container}",
-                RestoreError.INVALID_LOCATION,
+                RestoreError.INVALID_WIDGET_CONTAINER,
             )
             return
         }
@@ -500,7 +503,7 @@
                             ", appWidgetId=${c.appWidgetId}" +
                             ", component=${component}" +
                             ", restoreFlag:=${c.restoreFlag}",
-                        RestoreError.APP_NOT_INSTALLED,
+                        RestoreError.UNRESTORED_PENDING_WIDGET,
                     )
                     return
                 } else if (
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
index 3496c17..82eda36 100644
--- a/src/com/android/launcher3/model/data/AppPairInfo.kt
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -32,9 +32,8 @@
     }
 
     /** Convenience constructor, calls primary constructor and init block */
-    constructor(app1: WorkspaceItemInfo, app2: WorkspaceItemInfo) : this() {
-        add(app1)
-        add(app2)
+    constructor(apps: List<WorkspaceItemInfo>) : this() {
+        apps.forEach(this::add)
     }
 
     /** Creates a new AppPairInfo that is a copy of the provided one. */
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 8db981f..f56888b 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -124,11 +124,13 @@
         LauncherPrefs.get(context).removeSync(RESTORE_DEVICE);
 
         DeviceGridState deviceGridState = new DeviceGridState(context);
+        FileLog.d(TAG, "restoreIfNeeded: deviceGridState from context: " + deviceGridState);
         String oldPhoneFileName = deviceGridState.getDbFile();
         List<String> previousDbs = existingDbs(context);
         removeOldDBs(context, oldPhoneFileName);
         // The idp before this contains data about the old phone, after this it becomes the idp
         // of the current phone.
+        FileLog.d(TAG, "Resetting IDP to default for restore dest device");
         idp.reset(context);
         trySettingPreviousGridAsCurrent(context, idp, oldPhoneFileName, previousDbs);
     }
@@ -144,17 +146,24 @@
                 context, oldPhoneDbFileName);
         // The grid option could be null if current phone doesn't support the previous db.
         if (oldPhoneGridOption != null) {
+            FileLog.d(TAG, "trySettingPreviousGridAsCurrent:"
+                    + ", oldPhoneDbFileName: " + oldPhoneDbFileName
+                    + ", oldPhoneGridOption: " + oldPhoneGridOption
+                    + ", previousDbs: " + previousDbs);
 
             /* If the user only used the default db on the previous phone and the new default db is
              * bigger than or equal to the previous one, then keep the new default db */
             if (previousDbs.size() == 1 && oldPhoneGridOption.numColumns <= idp.numColumns
                     && oldPhoneGridOption.numRows <= idp.numRows) {
                 /* Keep the user in default grid */
+                FileLog.d(TAG, "Keeping default db from restore as current grid");
                 return;
             }
             /*
              * Here we are setting the previous db as the current one.
              */
+            FileLog.d(TAG, "Setting grid from old device as current grid: "
+                + "oldPhoneGridOption:" + oldPhoneGridOption.name);
             idp.setCurrentGrid(context, oldPhoneGridOption.name);
         }
     }
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 5068b48..6008287 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.BuildConfig.IS_DEBUG_DEVICE;
 import static com.android.launcher3.BuildConfig.IS_STUDIO_BUILD;
 import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_TABLET;
 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
 
 import android.app.Activity;
@@ -315,7 +316,9 @@
                     if (!Flags.oneGridSpecs()
                             // adding this condition until fixing b/378972567
                             || InvariantDeviceProfile.INSTANCE.get(getContext()).deviceType
-                            == TYPE_MULTI_DISPLAY) {
+                            == TYPE_MULTI_DISPLAY
+                            || InvariantDeviceProfile.INSTANCE.get(getContext()).deviceType
+                            == TYPE_TABLET) {
                         return false;
                     }
                     // When the setting changes rotate the screen accordingly to showcase the result
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 079191f..f21e5da 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -18,6 +18,7 @@
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME;
 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
 
 import android.content.Context;
@@ -29,6 +30,7 @@
 import android.view.View;
 
 import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.LauncherRootView;
@@ -54,6 +56,7 @@
 
     protected Configuration mOldConfig;
     private int mOldRotation;
+    private boolean mRecreateToUpdateTheme = false;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -63,6 +66,18 @@
         mOldRotation = WindowManagerProxy.INSTANCE.get(this).getRotation(this);
     }
 
+    @Override
+    protected void onSaveInstanceState(@NonNull Bundle outState) {
+        outState.putBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME, mRecreateToUpdateTheme);
+        super.onSaveInstanceState(outState);
+    }
+
+    @Override
+    protected void recreateToUpdateTheme() {
+        mRecreateToUpdateTheme = true;
+        super.recreateToUpdateTheme();
+    }
+
     /**
      * Create handlers to control the property changes for this activity
      */
diff --git a/src/com/android/launcher3/util/DynamicResource.java b/src/com/android/launcher3/util/DynamicResource.java
index fbdb5c2..fe9fd7c 100644
--- a/src/com/android/launcher3/util/DynamicResource.java
+++ b/src/com/android/launcher3/util/DynamicResource.java
@@ -22,35 +22,39 @@
 import androidx.annotation.FractionRes;
 import androidx.annotation.IntegerRes;
 
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.ResourceProvider;
 
+import javax.inject.Inject;
+
 /**
  * Utility class to support customizing resource values using plugins
  *
  * To load resources, call
- *    DynamicResource.provider(context).getInt(resId) or any other supported methods
+ * DynamicResource.provider(context).getInt(resId) or any other supported methods
  *
  * To allow customization for a particular resource, add them to dynamic_resources.xml
  */
+@LauncherAppSingleton
 public class DynamicResource implements
-        ResourceProvider, PluginListener<ResourceProvider>, SafeCloseable {
+        ResourceProvider, PluginListener<ResourceProvider> {
 
-    private static final MainThreadInitializedObject<DynamicResource> INSTANCE =
-            new MainThreadInitializedObject<>(DynamicResource::new);
+    private static final DaggerSingletonObject<DynamicResource> INSTANCE =
+            new DaggerSingletonObject<>(LauncherBaseAppComponent::getDynamicResource);
 
     private final Context mContext;
     private ResourceProvider mPlugin;
 
-    private DynamicResource(Context context) {
+    @Inject
+    public DynamicResource(@ApplicationContext Context context,
+            PluginManagerWrapper pluginManagerWrapper, DaggerSingletonTracker tracker) {
         mContext = context;
-        PluginManagerWrapper.INSTANCE.get(context).addPluginListener(this,
+        pluginManagerWrapper.addPluginListener(this,
                 ResourceProvider.class, false /* allowedMultiple */);
-    }
-
-    @Override
-    public void close() {
-        PluginManagerWrapper.INSTANCE.get(mContext).removePluginListener(this);
+        tracker.addCloseable(() -> pluginManagerWrapper.removePluginListener(this));
     }
 
     @Override
diff --git a/src/com/android/launcher3/util/coroutines/DispatcherProvider.kt b/src/com/android/launcher3/util/coroutines/DispatcherProvider.kt
index e9691a8..8877535 100644
--- a/src/com/android/launcher3/util/coroutines/DispatcherProvider.kt
+++ b/src/com/android/launcher3/util/coroutines/DispatcherProvider.kt
@@ -17,18 +17,44 @@
 package com.android.launcher3.util.coroutines
 
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.DelicateCoroutinesApi
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.newFixedThreadPoolContext
 
 interface DispatcherProvider {
     val default: CoroutineDispatcher
-    val io: CoroutineDispatcher
+    val background: CoroutineDispatcher
     val main: CoroutineDispatcher
     val unconfined: CoroutineDispatcher
 }
 
 object ProductionDispatchers : DispatcherProvider {
+    private val bgDispatcher = CoroutinesHelper.bgDispatcher()
+
     override val default: CoroutineDispatcher = Dispatchers.Default
-    override val io: CoroutineDispatcher = Dispatchers.IO
+    override val background: CoroutineDispatcher = bgDispatcher
     override val main: CoroutineDispatcher = Dispatchers.Main
     override val unconfined: CoroutineDispatcher = Dispatchers.Unconfined
 }
+
+private object CoroutinesHelper {
+    /**
+     * Default Coroutine dispatcher for background operations.
+     *
+     * Note that this is explicitly limiting the number of threads. In the past, we used
+     * [Dispatchers.IO]. This caused >40 threads to be spawned, and a lot of thread list lock
+     * contention between then, eventually causing jank.
+     */
+    @OptIn(DelicateCoroutinesApi::class)
+    fun bgDispatcher(): CoroutineDispatcher {
+        // Why a new ThreadPool instead of just using Dispatchers.IO with
+        // CoroutineDispatcher.limitedParallelism? Because, if we were to use Dispatchers.IO, we
+        // would share those threads with other dependencies using Dispatchers.IO.
+        // Using a dedicated thread pool we have guarantees only Launcher is able to schedule
+        // code on those.
+        return newFixedThreadPoolContext(
+            nThreads = Runtime.getRuntime().availableProcessors(),
+            name = "LauncherBg",
+        )
+    }
+}
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
index e100157..d4538dd 100644
--- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
+++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
@@ -15,12 +15,15 @@
  */
 package com.android.launcher3.widget;
 
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo;
+
+import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
@@ -30,17 +33,19 @@
 import android.os.Handler;
 import android.util.Log;
 import android.util.Size;
+import android.widget.RemoteViews;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.os.BuildCompat;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.icons.ShadowGenerator;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.util.CancellableTask;
@@ -52,20 +57,19 @@
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 
-/** Utility class to load widget previews */
+/**
+ * Utility class to generate widget previews
+ *
+ * Note that it no longer uses database, all previews are freshly generated
+ */
 public class DatabaseWidgetPreviewLoader {
 
     private static final String TAG = "WidgetPreviewLoader";
 
     private final Context mContext;
-    private final float mPreviewBoxCornerRadius;
 
     public DatabaseWidgetPreviewLoader(Context context) {
         mContext = context;
-        float previewCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context);
-        mPreviewBoxCornerRadius = previewCornerRadius > 0
-                ? previewCornerRadius
-                : mContext.getResources().getDimension(R.dimen.widget_preview_corner_radius);
     }
 
     /**
@@ -77,10 +81,10 @@
     public CancellableTask loadPreview(
             @NonNull WidgetItem item,
             @NonNull Size previewSize,
-            @NonNull Consumer<Bitmap> callback) {
+            @NonNull Consumer<WidgetPreviewInfo> callback) {
         Handler handler = getLoaderExecutor().getHandler();
-        CancellableTask<Bitmap> request = new CancellableTask<>(
-                () -> generatePreview(item, previewSize.getWidth(), previewSize.getHeight()),
+        CancellableTask<WidgetPreviewInfo> request = new CancellableTask<>(
+                () -> generatePreviewInfoBg(item, previewSize.getWidth(), previewSize.getHeight()),
                 MAIN_EXECUTOR,
                 callback);
         Utilities.postAsyncCallback(handler, request);
@@ -93,6 +97,39 @@
         return Executors.UI_HELPER_EXECUTOR;
     }
 
+    /** Generated the preview object. This method must be called on a background thread */
+    @VisibleForTesting
+    @NonNull
+    public WidgetPreviewInfo generatePreviewInfoBg(
+            WidgetItem item, int previewWidth, int previewHeight) {
+        WidgetPreviewInfo result = new WidgetPreviewInfo();
+
+        AppWidgetProviderInfo widgetInfo = item.widgetInfo;
+        if (BuildCompat.isAtLeastV() && Flags.enableGeneratedPreviews() && widgetInfo != null
+                && ((widgetInfo.generatedPreviewCategories & WIDGET_CATEGORY_HOME_SCREEN) != 0)) {
+            result.remoteViews = new WidgetManagerHelper(mContext)
+                    .loadGeneratedPreview(widgetInfo, WIDGET_CATEGORY_HOME_SCREEN);
+            if (result.remoteViews != null) {
+                result.providerInfo = widgetInfo;
+            }
+        }
+
+        if (result.providerInfo == null && widgetInfo != null
+                && widgetInfo.previewLayout != Resources.ID_NULL) {
+            result.providerInfo = fromProviderInfo(mContext, widgetInfo.clone());
+            // A hack to force the initial layout to be the preview layout since there is no API for
+            // rendering a preview layout for work profile apps yet. For non-work profile layout, a
+            // proper solution is to use RemoteViews(PackageName, LayoutId).
+            result.providerInfo.initialLayout = item.widgetInfo.previewLayout;
+        }
+
+        if (result.providerInfo == null) {
+            // fallback to bitmap preview
+            result.previewBitmap = generatePreview(item, previewWidth, previewHeight);
+        }
+        return result;
+    }
+
     /**
      * Returns a generated preview for a widget and if the preview should be saved in persistent
      * storage.
@@ -232,21 +269,6 @@
         });
     }
 
-    private RectF drawBoxWithShadow(Canvas c, int width, int height) {
-        Resources res = mContext.getResources();
-
-        ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.WHITE);
-        builder.shadowBlur = res.getDimension(R.dimen.widget_preview_shadow_blur);
-        builder.radius = mPreviewBoxCornerRadius;
-        builder.keyShadowDistance = res.getDimension(R.dimen.widget_preview_key_shadow_distance);
-
-        builder.bounds.set(builder.shadowBlur, builder.shadowBlur,
-                width - builder.shadowBlur,
-                height - builder.shadowBlur - builder.keyShadowDistance);
-        builder.drawShadow(c);
-        return builder.bounds;
-    }
-
     private Bitmap generateShortcutPreview(
             ShortcutConfigActivityInfo info, int maxWidth, int maxHeight) {
         int iconSize = ActivityContext.lookupContext(mContext).getDeviceProfile().allAppsIconSizePx;
@@ -280,4 +302,15 @@
             throw new RuntimeException(e);
         }
     }
+
+    /**
+     * Simple class to hold preview information
+     */
+    public static class WidgetPreviewInfo {
+
+        public AppWidgetProviderInfo providerInfo;
+        public RemoteViews remoteViews;
+
+        public Bitmap previewBitmap;
+    }
 }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 44ab966..b07d807 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -68,6 +68,8 @@
 
     private static final String TRACE_METHOD_NAME = "appwidget load-widget ";
 
+    private static final Integer NO_LAYOUT_ID = Integer.valueOf(0);
+
     private final CheckLongPressHelper mLongPressHelper;
     protected final ActivityContext mActivityContext;
 
@@ -121,7 +123,7 @@
     @Override
     public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
         super.setAppWidget(appWidgetId, info);
-        if (!mTrackingWidgetUpdate) {
+        if (!mTrackingWidgetUpdate && appWidgetId != -1) {
             mTrackingWidgetUpdate = true;
             Trace.beginAsyncSection(TRACE_METHOD_NAME + info.provider, appWidgetId);
             Log.i(TAG, "App widget created with id: " + appWidgetId);
@@ -164,6 +166,21 @@
         return false;
     }
 
+    private boolean isTaggedAsScrollable() {
+        // TODO: Introduce new api in AppWidgetHostView to indicate whether the widget is
+        // scrollable.
+        for (int i = 0; i < this.getChildCount(); i++) {
+            View child = this.getChildAt(i);
+            final Integer layoutId = (Integer) child.getTag(android.R.id.widget_frame);
+            if (layoutId != null) {
+                // The layout id is only set to 0 when RemoteViews is created from
+                // DrawInstructions.
+                return NO_LAYOUT_ID.equals(layoutId);
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns true if the application of {@link RemoteViews} through {@link #updateAppWidget} are
      * currently being deferred.
@@ -238,16 +255,6 @@
     }
 
     @Override
-    public AppWidgetProviderInfo getAppWidgetInfo() {
-        AppWidgetProviderInfo info = super.getAppWidgetInfo();
-        if (info != null && !(info instanceof LauncherAppWidgetProviderInfo)) {
-            throw new IllegalStateException("Launcher widget must have"
-                    + " LauncherAppWidgetProviderInfo");
-        }
-        return info;
-    }
-
-    @Override
     public void getFocusedRect(Rect r) {
         super.getFocusedRect(r);
         // Outset to a larger rect for drawing a padding between focus outline and widget
@@ -266,7 +273,7 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
-        mIsScrollable = checkScrollableRecursively(this);
+        mIsScrollable = isTaggedAsScrollable() || checkScrollableRecursively(this);
     }
 
     /**
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index b7ad95e..4811a17 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -16,17 +16,17 @@
 
 package com.android.launcher3.widget;
 
-import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 
 import static com.android.launcher3.Flags.enableWidgetTapToAdd;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
-import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo;
 import static com.android.launcher3.widget.util.WidgetSizes.getWidgetItemSizePx;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
+import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
@@ -40,19 +40,17 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Button;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.RemoteViews;
 import android.widget.TextView;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.app.animation.Interpolators;
 import com.android.launcher3.CheckLongPressHelper;
-import com.android.launcher3.Flags;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatedPropertySetter;
@@ -63,11 +61,10 @@
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.util.CancellableTask;
 import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.DatabaseWidgetPreviewLoader.WidgetPreviewInfo;
 import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize;
 import com.android.launcher3.widget.util.WidgetSizes;
 
-import java.util.function.Consumer;
-
 /**
  * Represents the individual cell of the widget inside the widget tray. The preview is drawn
  * horizontally centered, and scaled down if needed.
@@ -154,7 +151,21 @@
         mWidgetDescription = findViewById(R.id.widget_description);
         mWidgetTextContainer = findViewById(R.id.widget_text_container);
         mWidgetAddButton = findViewById(R.id.widget_add_button);
+
         if (enableWidgetTapToAdd()) {
+
+            setAccessibilityDelegate(new AccessibilityDelegate() {
+                @Override
+                public void onInitializeAccessibilityNodeInfo(View host,
+                        AccessibilityNodeInfo info) {
+                    super.onInitializeAccessibilityNodeInfo(host, info);
+                    String accessibilityLabel = getResources().getString(mWidgetAddButton.isShown()
+                            ? R.string.widget_cell_tap_to_hide_add_button_label
+                            : R.string.widget_cell_tap_to_show_add_button_label);
+                    info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK,
+                            accessibilityLabel));
+                }
+            });
             mWidgetAddButton.setVisibility(INVISIBLE);
         }
     }
@@ -226,17 +237,6 @@
      * Applies the item to this view
      */
     public void applyFromCellItem(WidgetItem item) {
-        applyFromCellItem(item, this::applyPreview, /*cachedPreview=*/null);
-    }
-
-    /**
-     * Applies the item to this view
-     * @param item item to apply
-     * @param callback callback when preview is loaded in case the preview is being loaded or cached
-     * @param cachedPreview previously cached preview bitmap is present
-     */
-    public void applyFromCellItem(WidgetItem item, @NonNull Consumer<Bitmap> callback,
-            @Nullable Bitmap cachedPreview) {
         Context context = getContext();
         mItem = item;
         mWidgetSize = getWidgetItemSizePx(getContext(), mActivity.getDeviceProfile(), mItem);
@@ -267,37 +267,28 @@
         }
 
         if (mRemoteViewsPreview != null) {
-            mAppWidgetHostViewPreview = createAppWidgetHostView(context);
-            setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, item.widgetInfo,
-                    mRemoteViewsPreview);
-        } else if (Flags.enableGeneratedPreviews()
-                && item.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)) {
-            mAppWidgetHostViewPreview = createAppWidgetHostView(context);
-            setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, item.widgetInfo,
-                    item.generatedPreviews.get(WIDGET_CATEGORY_HOME_SCREEN));
-        } else if (item.hasPreviewLayout()) {
-            // If the context is a Launcher activity, DragView will show mAppWidgetHostViewPreview
-            // as a preview during drag & drop. And thus, we should use LauncherAppWidgetHostView,
-            // which supports applying local color extraction during drag & drop.
-            mAppWidgetHostViewPreview = isLauncherContext(context)
-                    ? new LauncherAppWidgetHostView(context)
-                    : createAppWidgetHostView(context);
-            LauncherAppWidgetProviderInfo providerInfo =
-                    fromProviderInfo(context, item.widgetInfo.clone());
-            // A hack to force the initial layout to be the preview layout since there is no API for
-            // rendering a preview layout for work profile apps yet. For non-work profile layout, a
-            // proper solution is to use RemoteViews(PackageName, LayoutId).
-            providerInfo.initialLayout = item.widgetInfo.previewLayout;
-            setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, providerInfo, null);
-        } else if (cachedPreview != null) {
-            applyPreview(cachedPreview);
+            WidgetPreviewInfo previewInfo = new WidgetPreviewInfo();
+            previewInfo.providerInfo = item.widgetInfo;
+            previewInfo.remoteViews = mRemoteViewsPreview;
+            applyPreview(previewInfo);
         } else {
             if (mActiveRequest == null) {
-                mActiveRequest = mWidgetPreviewLoader.loadPreview(mItem, mWidgetSize, callback);
+                mActiveRequest = mWidgetPreviewLoader.loadPreview(
+                        mItem, mWidgetSize, this::applyPreview);
             }
         }
     }
 
+    private void applyPreview(WidgetPreviewInfo previewInfo) {
+        if (previewInfo.providerInfo != null) {
+            mAppWidgetHostViewPreview = createAppWidgetHostView(getContext());
+            setAppWidgetHostViewPreview(mAppWidgetHostViewPreview, previewInfo.providerInfo,
+                    previewInfo.remoteViews);
+        } else {
+            applyBitmapPreview(previewInfo.previewBitmap);
+        }
+    }
+
     private void initPreviewContainerSizeAndScale() {
         WidgetPreviewContainerSize previewSize = WidgetPreviewContainerSize.Companion.forItem(mItem,
                 mActivity.getDeviceProfile());
@@ -321,7 +312,7 @@
 
     private void setAppWidgetHostViewPreview(
             NavigableAppWidgetHostView appWidgetHostViewPreview,
-            LauncherAppWidgetProviderInfo providerInfo,
+            AppWidgetProviderInfo providerInfo,
             @Nullable RemoteViews remoteViews) {
         appWidgetHostViewPreview.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
         appWidgetHostViewPreview.setAppWidget(/* appWidgetId= */ -1, providerInfo);
@@ -333,7 +324,7 @@
                 mWidgetSize.getWidth(), mWidgetSize.getHeight(), Gravity.CENTER);
         mWidgetImageContainer.addView(appWidgetHostViewPreview, /* index= */ 0, widgetHostLP);
         mWidgetImage.setVisibility(View.GONE);
-        applyPreview(null);
+        applyBitmapPreview(null);
 
         appWidgetHostViewPreview.addOnLayoutChangeListener(
                 (v, l, t, r, b, ol, ot, or, ob) ->
@@ -391,7 +382,7 @@
         mAnimatePreview = shouldAnimate;
     }
 
-    private void applyPreview(Bitmap bitmap) {
+    private void applyBitmapPreview(Bitmap bitmap) {
         if (bitmap != null) {
             Drawable drawable = new RoundDrawableWrapper(
                     new FastBitmapDrawable(bitmap), mEnforcedCornerRadius);
@@ -480,8 +471,8 @@
         mLongPressHelper.cancelLongPress();
     }
 
-    private static NavigableAppWidgetHostView createAppWidgetHostView(Context context) {
-        return new NavigableAppWidgetHostView(context) {
+    private static LauncherAppWidgetHostView createAppWidgetHostView(Context context) {
+        return new LauncherAppWidgetHostView(context) {
             @Override
             protected boolean shouldAllowDirectClick() {
                 return false;
@@ -489,10 +480,6 @@
         };
     }
 
-    private static boolean isLauncherContext(Context context) {
-        return ActivityContext.lookupContext(context) instanceof Launcher;
-    }
-
     @Override
     public CharSequence getAccessibilityClassName() {
         return WidgetCell.class.getName();
diff --git a/src/com/android/launcher3/widget/WidgetInflater.kt b/src/com/android/launcher3/widget/WidgetInflater.kt
index 271c9c2..d6cadc7 100644
--- a/src/com/android/launcher3/widget/WidgetInflater.kt
+++ b/src/com/android/launcher3/widget/WidgetInflater.kt
@@ -30,16 +30,14 @@
 
     private val widgetHelper = WidgetManagerHelper(context)
 
-    fun inflateAppWidget(
-        item: LauncherAppWidgetInfo,
-    ): InflationResult {
+    fun inflateAppWidget(item: LauncherAppWidgetInfo): InflationResult {
         if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) {
             item.providerName = QsbContainerView.getSearchComponentName(context)
             if (item.providerName == null) {
                 return InflationResult(
                     TYPE_DELETE,
                     reason = "search widget removed because search component cannot be found",
-                    restoreErrorType = RestoreError.NO_SEARCH_WIDGET
+                    restoreErrorType = RestoreError.NO_SEARCH_WIDGET,
                 )
             }
         }
@@ -48,7 +46,7 @@
         }
         val appWidgetInfo: LauncherAppWidgetProviderInfo?
         var removalReason = ""
-        @RestoreError var logReason = RestoreError.APP_NOT_INSTALLED
+        @RestoreError var logReason = RestoreError.OTHER_WIDGET_INFLATION_FAIL
         var update = false
 
         if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
@@ -74,7 +72,7 @@
             if (appWidgetInfo == null) {
                 if (item.appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
                     removalReason = "CustomWidgetManager cannot find provider from that widget id."
-                    logReason = RestoreError.MISSING_INFO
+                    logReason = RestoreError.INVALID_CUSTOM_WIDGET_ID
                 } else {
                     removalReason =
                         ("AppWidgetManager cannot find provider for that widget id." +
@@ -96,7 +94,7 @@
                     type = TYPE_DELETE,
                     reason =
                         "Removing restored widget: id=${item.appWidgetId} belongs to component ${item.providerName} user ${item.user}, as the provider is null and $removalReason",
-                    restoreErrorType = logReason
+                    restoreErrorType = logReason,
                 )
             }
 
@@ -132,7 +130,7 @@
                         widgetHelper.bindAppWidgetIdIfAllowed(
                             item.appWidgetId,
                             appWidgetInfo,
-                            options
+                            options,
                         )
 
                     // We tried to bind once. If we were not able to bind, we would need to
@@ -189,9 +187,10 @@
     data class InflationResult(
         val type: Int,
         val reason: String? = null,
-        @RestoreError val restoreErrorType: String = RestoreError.APP_NOT_INSTALLED,
+        @RestoreError
+        val restoreErrorType: String = RestoreError.UNSPECIFIED_WIDGET_INFLATION_RESULT,
         val isUpdate: Boolean = false,
-        val widgetInfo: LauncherAppWidgetProviderInfo? = null
+        val widgetInfo: LauncherAppWidgetProviderInfo? = null,
     )
 
     companion object {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 150806a..d850fc6 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -24,6 +24,7 @@
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 import static com.android.launcher3.views.RecyclerViewFastScroller.FastScrollerLocation.WIDGET_SCROLLER;
 
+import static java.lang.Math.abs;
 import static java.util.Collections.emptyList;
 
 import android.animation.Animator;
@@ -41,6 +42,7 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.WindowInsets;
@@ -119,6 +121,10 @@
     protected int mRecommendationsCurrentPage = 0;
     protected final SparseArray<AdapterHolder> mAdapters = new SparseArray();
 
+    // Helps with removing focus from searchbar by analyzing motion events.
+    private final SearchClearFocusHelper mSearchClearFocusHelper = new SearchClearFocusHelper();
+    private final float mTouchSlop; // initialized in constructor
+
     private final OnAttachStateChangeListener mBindScrollbarInSearchMode =
             new OnAttachStateChangeListener() {
                 @Override
@@ -165,6 +171,7 @@
 
     public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         mDeviceProfile = mActivityContext.getDeviceProfile();
         mUserCache = UserCache.INSTANCE.get(context);
         mHasWorkProfile = mUserCache.getUserProfiles()
@@ -714,10 +721,14 @@
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mNoIntercept = shouldScroll(ev);
-            if (mSearchBar.isSearchBarFocused()
-                    && !getPopupContainer().isEventOverView(mSearchBarContainer, ev)) {
-                mSearchBar.clearSearchBarFocus();
-            }
+        }
+
+        // Clear focus only if user touched outside of search area and handling focus out ourselves
+        // was necessary (e.g. when it's not predictive back, but other user interaction).
+        if (mSearchBar.isSearchBarFocused()
+                && !getPopupContainer().isEventOverView(mSearchBarContainer, ev)
+                && mSearchClearFocusHelper.shouldClearFocus(ev, mTouchSlop)) {
+            mSearchBar.clearSearchBarFocus();
         }
 
         return super.onControllerInterceptTouchEvent(ev);
@@ -1141,4 +1152,53 @@
             mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(mMaxSpanPerRow);
         }
     }
+
+    /**
+     * Helper to identify if searchbar's focus can be cleared when user performs an action
+     * outside search.
+     */
+    private static class SearchClearFocusHelper {
+        private float mFirstInteractionX = -1f;
+        private float mFirstInteractionY = -1f;
+
+        /**
+         * For a given [MotionEvent] indicates if we should clear focus from search (and hide IME).
+         */
+        boolean shouldClearFocus(MotionEvent ev, float touchSlop) {
+            int action = ev.getAction();
+            boolean clearFocus = false;
+
+            if (action == MotionEvent.ACTION_DOWN) {
+                mFirstInteractionX = ev.getX();
+                mFirstInteractionY = ev.getY();
+            } else if (action == MotionEvent.ACTION_CANCEL) {
+                // This is when user performed a gesture e.g. predictive back
+                // We don't handle it ourselves and let IME handle the close.
+                mFirstInteractionY = -1;
+                mFirstInteractionX = -1;
+            } else if (action == MotionEvent.ACTION_UP) {
+                // Its clear that user action wasn't predictive back - but press / scroll etc. that
+                // should hide the keyboard.
+                clearFocus = true;
+                mFirstInteractionY = -1;
+                mFirstInteractionX = -1;
+            } else if (action == MotionEvent.ACTION_MOVE) {
+                // Sometimes, on move, we may not receive ACTION_UP, but if the move was within
+                // touch slop and we didn't know if its moved or cancelled, we can clear focus.
+                // Example case: Apps list is small and you do a little scroll on list - in such, we
+                // want to still hide the keyboard.
+                if (mFirstInteractionX != -1 && mFirstInteractionY != -1) {
+                    float distY = abs(mFirstInteractionY - ev.getY());
+                    float distX = abs(mFirstInteractionX - ev.getX());
+                    if (distY >= touchSlop || distX >= touchSlop) {
+                        clearFocus = true;
+                        mFirstInteractionY = -1;
+                        mFirstInteractionX = -1;
+                    }
+                }
+            }
+
+            return clearFocus;
+        }
+    }
 }
diff --git a/tests/Launcher3Tests.xml b/tests/Launcher3Tests.xml
index 270a610..a876860 100644
--- a/tests/Launcher3Tests.xml
+++ b/tests/Launcher3Tests.xml
@@ -48,6 +48,8 @@
 
         <option name="run-command" value="settings put system pointer_location 1" />
         <option name="run-command" value="settings put system show_touches 1" />
+
+        <option name="run-command" value="setprop pixel_legal_joint_permission_v2 true" />
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/folder/FolderNameProviderTest.java
index 9c15309..b6fa81c 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/FolderNameProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/FolderNameProviderTest.java
@@ -23,6 +23,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Process;
 import android.os.UserHandle;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -49,16 +50,17 @@
     @Before
     public void setUp() {
         mContext = new ActivityContextWrapper(getApplicationContext());
+        int workUserId = Process.myUserHandle().hashCode() + 1;
         mItem1 = new WorkspaceItemInfo(new AppInfo(
                 new ComponentName("a.b.c", "a.b.c/a.b.c.d"),
                 "title1",
-                UserHandle.of(10),
+                UserHandle.of(workUserId),
                 new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d"))
         ));
         mItem2 = new WorkspaceItemInfo(new AppInfo(
                 new ComponentName("a.b.c", "a.b.c/a.b.c.d"),
                 "title2",
-                UserHandle.of(10),
+                UserHandle.of(workUserId),
                 new Intent().setComponent(new ComponentName("a.b.c", "a.b.c/a.b.c.d"))
         ));
     }
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index 111ffaa..7f481b7 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -18,6 +18,7 @@
 
 import android.R
 import android.content.Context
+import android.graphics.Bitmap
 import android.os.Process
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -26,9 +27,9 @@
 import com.android.launcher3.LauncherPrefs.Companion.THEMED_ICONS
 import com.android.launcher3.LauncherPrefs.Companion.get
 import com.android.launcher3.graphics.PreloadIconDrawable
-import com.android.launcher3.icons.BaseIconFactory
 import com.android.launcher3.icons.FastBitmapDrawable
 import com.android.launcher3.icons.UserBadgeDrawable
+import com.android.launcher3.icons.mono.MonoThemedBitmap
 import com.android.launcher3.model.data.FolderInfo
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED
@@ -88,54 +89,26 @@
         // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps
         val folderApps = modelHelper.bgDataModel.collections.valueAt(0).getAppContents()
         // Set first icon to be themed.
-        folderApps[0]
-            .bitmap
-            .setMonoIcon(
+        folderApps[0].bitmap.themedBitmap =
+            MonoThemedBitmap(
                 folderApps[0].bitmap.icon,
-                BaseIconFactory(
-                    context,
-                    context.resources.configuration.densityDpi,
-                    previewItemManager.mIconSize,
-                ),
+                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
             )
 
         // Set second icon to be non-themed.
-        folderApps[1]
-            .bitmap
-            .setMonoIcon(
-                null,
-                BaseIconFactory(
-                    context,
-                    context.resources.configuration.densityDpi,
-                    previewItemManager.mIconSize,
-                ),
-            )
+        folderApps[1].bitmap.themedBitmap = null
 
         // Set third icon to be themed with badge.
-        folderApps[2]
-            .bitmap
-            .setMonoIcon(
+        folderApps[2].bitmap.themedBitmap =
+            MonoThemedBitmap(
                 folderApps[2].bitmap.icon,
-                BaseIconFactory(
-                    context,
-                    context.resources.configuration.densityDpi,
-                    previewItemManager.mIconSize,
-                ),
+                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
             )
         folderApps[2].bitmap = folderApps[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
 
         // Set fourth icon to be non-themed with badge.
         folderApps[3].bitmap = folderApps[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
-        folderApps[3]
-            .bitmap
-            .setMonoIcon(
-                null,
-                BaseIconFactory(
-                    context,
-                    context.resources.configuration.densityDpi,
-                    previewItemManager.mIconSize,
-                ),
-            )
+        folderApps[3].bitmap.themedBitmap = null
 
         defaultThemedIcons = get(context).get(THEMED_ICONS)
     }
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
new file mode 100644
index 0000000..4af564e
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons.mono
+
+import android.graphics.Color
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.ColorDrawable
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import android.util.DisplayMetrics
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.Flags
+import com.android.launcher3.icons.BaseIconFactory
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assume.assumeFalse
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MonoIconThemeControllerTest {
+
+    @get:Rule val mSetFlagsRule = SetFlagsRule()
+
+    private val iconFactory = BaseIconFactory(context, DisplayMetrics.DENSITY_MEDIUM, 30)
+
+    @Test
+    fun `createThemedBitmap when mono drawable is present`() {
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+        assertNotNull(
+            MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory)
+        )
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS)
+    fun `createThemedBitmap when mono generation is disabled`() {
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
+        assertNull(
+            MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory)
+        )
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS)
+    fun `createThemedBitmap when mono generation is enabled`() {
+        ensureBitmapSerializationSupported()
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
+        assertNotNull(
+            MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory)
+        )
+    }
+
+    @Test
+    fun `decode bitmap after serialization valid data`() {
+        ensureBitmapSerializationSupported()
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+        val iconInfo = iconFactory.createBadgedIconBitmap(icon)
+
+        val themeBitmap =
+            MonoIconThemeController().createThemedBitmap(icon, iconInfo, iconFactory)!!
+        assertNotNull(
+            MonoIconThemeController().decode(themeBitmap.serialize(), iconInfo, iconFactory)
+        )
+    }
+
+    @Test
+    fun `decode bitmap after serialization invalid data`() {
+        ensureBitmapSerializationSupported()
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+        val iconInfo = iconFactory.createBadgedIconBitmap(icon)
+        assertNull(MonoIconThemeController().decode(byteArrayOf(1, 1, 1, 1), iconInfo, iconFactory))
+    }
+
+    @Test
+    fun `createThemedAdaptiveIcon with monochrome drawable`() {
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+        assertNotNull(MonoIconThemeController().createThemedAdaptiveIcon(context, icon, null))
+    }
+
+    @Test
+    fun `createThemedAdaptiveIcon with bitmap info`() {
+        val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
+        val iconInfo = iconFactory.createBadgedIconBitmap(icon)
+        iconInfo.themedBitmap =
+            MonoIconThemeController().createThemedBitmap(icon, iconInfo, iconFactory)
+
+        val nonMonoIcon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
+        assertNotNull(
+            MonoIconThemeController().createThemedAdaptiveIcon(context, nonMonoIcon, iconInfo)
+        )
+    }
+
+    @Test
+    fun `createThemedAdaptiveIcon invalid bitmap info`() {
+        val nonMonoIcon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
+        assertNull(
+            MonoIconThemeController()
+                .createThemedAdaptiveIcon(context, nonMonoIcon, BitmapInfo.LOW_RES_INFO)
+        )
+    }
+
+    companion object {
+
+        fun ensureBitmapSerializationSupported() {
+            // Robolectric doesn't support serializing 8-bit bitmaps
+            assumeFalse(isRunningInRobolectric)
+        }
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoThemedBitmapTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoThemedBitmapTest.kt
new file mode 100644
index 0000000..32de9e9
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoThemedBitmapTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons.mono
+
+import android.graphics.Bitmap
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.mono.MonoIconThemeControllerTest.Companion.ensureBitmapSerializationSupported
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MonoThemedBitmapTest {
+
+    @Test
+    fun `newDrawable returns valid drawable`() {
+        val bitmap =
+            MonoThemedBitmap(
+                Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8),
+                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
+            )
+        val d = bitmap.newDrawable(BitmapInfo.LOW_RES_INFO, context)
+        assertTrue(d is ThemedIconDrawable)
+    }
+
+    @Test
+    fun `serialize returns valid bytes`() {
+        ensureBitmapSerializationSupported()
+        val bitmap =
+            MonoThemedBitmap(
+                Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8),
+                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
+            )
+        assertTrue(bitmap.serialize().isNotEmpty())
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
index 9cc380e..db37247 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
@@ -20,7 +20,7 @@
 import android.content.ComponentName
 import android.content.Intent
 import android.content.pm.PackageInstaller.SessionInfo
-import android.os.UserHandle
+import android.os.Process.myUserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
@@ -77,16 +77,18 @@
             SessionInfo().apply {
                 installerPackageName = expectedInstallerPackage
                 appPackageName = expectedAppPackage
+                userId = myUserHandle().identifier
             }
         val sessionInfoUnexpected =
             SessionInfo().apply {
                 installerPackageName = expectedInstallerPackage
                 appPackageName = unexpectedAppPackage
+                userId = myUserHandle().identifier
             }
         val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> =
             hashMapOf(
-                PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected,
-                PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected,
+                PackageUserKey(unexpectedAppPackage, myUserHandle()) to sessionInfoExpected,
+                PackageUserKey(expectedAppPackage, myUserHandle()) to sessionInfoUnexpected,
             )
 
         // When
@@ -200,16 +202,18 @@
             SessionInfo().apply {
                 installerPackageName = expectedInstallerPackage
                 appPackageName = expectedAppPackage
+                userId = myUserHandle().identifier
             }
         val sessionInfoUnexpected =
             SessionInfo().apply {
                 installerPackageName = expectedInstallerPackage
                 appPackageName = unexpectedAppPackage
+                userId = myUserHandle().identifier
             }
         val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> =
             hashMapOf(
-                PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected,
-                PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected,
+                PackageUserKey(unexpectedAppPackage, myUserHandle()) to sessionInfoExpected,
+                PackageUserKey(expectedAppPackage, myUserHandle()) to sessionInfoUnexpected,
             )
         val expectedItemInfo = WorkspaceItemInfo().apply { intent = expectedIntent }
         val expectedFolderInfo = FolderInfo().apply { add(expectedItemInfo) }
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index ed8b397..7099d38 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -36,10 +36,7 @@
 import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
 import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
 import com.android.launcher3.Utilities.EMPTY_PERSON_ARRAY
-import com.android.launcher3.backuprestore.LauncherRestoreEventLogger
-import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.MISSING_INFO
-import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.MISSING_WIDGET_PROVIDER
-import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.PROFILE_DELETED
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
 import com.android.launcher3.icons.CacheableShortcutInfo
 import com.android.launcher3.model.data.FolderInfo
 import com.android.launcher3.model.data.IconRequestInfo
@@ -224,7 +221,8 @@
         itemProcessorUnderTest.processItem()
 
         // Then
-        verify(mockCursor).markDeleted("User has been deleted for item id=1", PROFILE_DELETED)
+        verify(mockCursor)
+            .markDeleted("User has been deleted for item id=1", RestoreError.PROFILE_DELETED)
         verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull())
     }
 
@@ -237,7 +235,8 @@
         itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
         itemProcessorUnderTest.processItem()
         // Then
-        verify(mockCursor).markDeleted("Null intent from db for item id=1", MISSING_INFO)
+        verify(mockCursor)
+            .markDeleted("Null intent from db for item id=1", RestoreError.APP_NO_DB_INTENT)
         verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull())
     }
 
@@ -255,7 +254,8 @@
         itemProcessorUnderTest.processItem()
 
         // Then
-        verify(mockCursor).markDeleted("No target package for item id=1", MISSING_INFO)
+        verify(mockCursor)
+            .markDeleted("No target package for item id=1", RestoreError.APP_NO_TARGET_PACKAGE)
         verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull())
     }
 
@@ -272,7 +272,8 @@
         itemProcessorUnderTest.processItem()
 
         // Then
-        verify(mockCursor).markDeleted("No target package for item id=1", MISSING_INFO)
+        verify(mockCursor)
+            .markDeleted("No target package for item id=1", RestoreError.APP_NO_TARGET_PACKAGE)
         verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull())
     }
 
@@ -350,7 +351,7 @@
                     " targetPkg=package," +
                     " component=ComponentInfo{package/class}." +
                     " Unable to create launch Intent.",
-                MISSING_INFO,
+                RestoreError.APP_NO_LAUNCH_INTENT,
             )
         verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull())
     }
@@ -663,7 +664,7 @@
         verify(mockCursor)
             .markDeleted(
                 "processWidget: Unrestored Pending widget removed: id=1, appWidgetId=0, component=$expectedComponentName, restoreFlag:=4",
-                LauncherRestoreEventLogger.RestoreError.APP_NOT_INSTALLED,
+                RestoreError.UNRESTORED_PENDING_WIDGET,
             )
     }
 
@@ -689,7 +690,7 @@
                 type = WidgetInflater.TYPE_DELETE,
                 widgetInfo = null,
                 reason = "test_delete_reason",
-                restoreErrorType = MISSING_WIDGET_PROVIDER,
+                restoreErrorType = RestoreError.MISSING_WIDGET_PROVIDER,
             )
         mockWidgetInflater =
             mock<WidgetInflater>().apply {
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestDispatcherProvider.kt b/tests/multivalentTests/src/com/android/launcher3/util/TestDispatcherProvider.kt
index 39e1ec5..3319c53 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestDispatcherProvider.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestDispatcherProvider.kt
@@ -21,7 +21,7 @@
 
 class TestDispatcherProvider(testDispatcher: CoroutineDispatcher) : DispatcherProvider {
     override val default: CoroutineDispatcher = testDispatcher
-    override val io: CoroutineDispatcher = testDispatcher
+    override val background: CoroutineDispatcher = testDispatcher
     override val main: CoroutineDispatcher = testDispatcher
     override val unconfined: CoroutineDispatcher = testDispatcher
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
index deb0ef3..a87a208 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
@@ -21,6 +21,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.os.Bundle;
+import android.os.Process;
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -86,6 +87,7 @@
     public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
         ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.applicationInfo = new ApplicationInfo();
+        activityInfo.applicationInfo.uid = Process.myUid();
         AppWidgetProviderInfo info = new AppWidgetProviderInfo();
         info.providerInfo = activityInfo;
         info.provider = cn;
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
index ac5fda2..b92582c 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
@@ -1,9 +1,8 @@
 package com.android.launcher3.widget
 
+import android.appwidget.AppWidgetManager
 import android.appwidget.AppWidgetProviderInfo
 import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
-import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
-import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.ActivityInfo
@@ -14,23 +13,30 @@
 import android.view.ContextThemeWrapper
 import android.view.LayoutInflater
 import android.widget.RemoteViews
-import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import com.android.launcher3.Flags.FLAG_ENABLE_GENERATED_PREVIEWS
 import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.R
 import com.android.launcher3.icons.IconCache
-import com.android.launcher3.icons.IconProvider
 import com.android.launcher3.model.WidgetItem
 import com.android.launcher3.util.ActivityContextWrapper
 import com.android.launcher3.util.Executors
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.android.launcher3.util.TestUtil
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -46,28 +52,25 @@
         getInstrumentation().context.run {
             resources.getIdentifier("test_layout_appwidget_blue", "layout", packageName)
         }
-    private lateinit var context: Context
+
+    private lateinit var context: SandboxModelContext
+    private lateinit var uiContext: Context
     private lateinit var generatedPreview: RemoteViews
     private lateinit var widgetCell: WidgetCell
-    private lateinit var helper: WidgetManagerHelper
     private lateinit var appWidgetProviderInfo: LauncherAppWidgetProviderInfo
     private lateinit var widgetItem: WidgetItem
-    private lateinit var iconCache: IconCache
+
+    @Mock lateinit var iconCache: IconCache
 
     @Before
     fun setup() {
-        context = getApplicationContext()
+        MockitoAnnotations.initMocks(this)
+        context = SandboxModelContext()
         generatedPreview = RemoteViews(context.packageName, generatedPreviewLayout)
+        uiContext =
+            ActivityContextWrapper(ContextThemeWrapper(context, R.style.WidgetContainerTheme))
         widgetCell =
-            LayoutInflater.from(
-                    ActivityContextWrapper(
-                        ContextThemeWrapper(
-                            context,
-                            com.android.launcher3.R.style.WidgetContainerTheme,
-                        )
-                    )
-                )
-                .inflate(com.android.launcher3.R.layout.widget_cell, null) as WidgetCell
+            LayoutInflater.from(uiContext).inflate(R.layout.widget_cell, null) as WidgetCell
         appWidgetProviderInfo =
             AppWidgetProviderInfo()
                 .apply {
@@ -76,72 +79,52 @@
                     providerInfo = ActivityInfo().apply { applicationInfo = ApplicationInfo() }
                 }
                 .let { LauncherAppWidgetProviderInfo.fromProviderInfo(context, it) }
-        helper =
-            object : WidgetManagerHelper(context) {
-                override fun loadGeneratedPreview(
-                    info: AppWidgetProviderInfo,
-                    widgetCategory: Int,
-                ) =
-                    generatedPreview.takeIf {
-                        info === appWidgetProviderInfo &&
-                            widgetCategory == WIDGET_CATEGORY_HOME_SCREEN
-                    }
+
+        val widgetManager = context.spyService(AppWidgetManager::class.java)
+        doAnswer { i ->
+                generatedPreview.takeIf {
+                    i.arguments[0] == appWidgetProviderInfo.provider &&
+                        i.arguments[1] == appWidgetProviderInfo.user &&
+                        i.arguments[2] == WIDGET_CATEGORY_HOME_SCREEN
+                }
             }
+            .whenever(widgetManager)
+            .getWidgetPreview(any(), any(), any())
         createWidgetItem()
     }
 
     @After
     fun tearDown() {
-        iconCache.close()
+        context.destroy()
     }
 
     private fun createWidgetItem() {
         Executors.MODEL_EXECUTOR.submit {
                 val idp = InvariantDeviceProfile()
-                if (::iconCache.isInitialized) iconCache.close()
-                iconCache = IconCache(context, idp, null, IconProvider(context))
-                widgetItem = WidgetItem(appWidgetProviderInfo, idp, iconCache, context, helper)
+                widgetItem = WidgetItem(appWidgetProviderInfo, idp, iconCache, context)
             }
             .get()
     }
 
     @Test
-    fun widgetItem_hasGeneratedPreview() {
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isTrue()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
-    }
-
-    @Test
     fun widgetItem_hasGeneratedPreview_noPreview() {
         appWidgetProviderInfo.generatedPreviewCategories = 0
         createWidgetItem()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isFalse()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
-    }
-
-    @Test
-    fun widgetItem_hasGeneratedPreview_nullPreview() {
-        appWidgetProviderInfo.generatedPreviewCategories =
-            WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD
-        createWidgetItem()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isTrue()
-        // loadGeneratedPreview returns null for KEYGUARD, so this should still be false.
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
-        assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
+        val preview = DatabaseWidgetPreviewLoader(uiContext).generatePreviewInfoBg(widgetItem, 1, 1)
+        assertThat(preview.remoteViews).isNull()
     }
 
     @Test
     fun widgetItem_getGeneratedPreview() {
-        val preview = widgetItem.generatedPreviews.get(WIDGET_CATEGORY_HOME_SCREEN)
-        assertThat(preview).isEqualTo(generatedPreview)
+        val preview = DatabaseWidgetPreviewLoader(uiContext).generatePreviewInfoBg(widgetItem, 1, 1)
+        assertThat(preview.remoteViews).isEqualTo(generatedPreview)
     }
 
     @Test
     fun widgetCell_showGeneratedPreview() {
         widgetCell.applyFromCellItem(widgetItem)
-        DatabaseWidgetPreviewLoader.getLoaderExecutor().submit {}.get()
+        TestUtil.runOnExecutorSync(DatabaseWidgetPreviewLoader.getLoaderExecutor()) {}
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
         assertThat(widgetCell.appWidgetHostViewPreview).isNotNull()
         assertThat(widgetCell.appWidgetHostViewPreview?.appWidgetInfo)
             .isEqualTo(appWidgetProviderInfo)
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
index 8b6553f..ac67d2b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
@@ -148,11 +148,12 @@
 
         doAnswer(invocation -> widgetLabel).when(mIconCache).getTitleNoCache(any());
 
-        AppWidgetProviderInfo providerInfo = WidgetUtils.createAppWidgetProviderInfo(ComponentName
-                .createRelative(TEST_PACKAGE, widgetClassName));
+        AppWidgetProviderInfo providerInfo = WidgetUtils.createAppWidgetProviderInfo(
+                ComponentName.createRelative(TEST_PACKAGE, widgetClassName));
 
         LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
-                LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, providerInfo);
+                spy(LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, providerInfo));
+        doReturn(Process.myUserHandle()).when(launcherAppWidgetProviderInfo).getProfile();
         launcherAppWidgetProviderInfo.spanX = 2;
         launcherAppWidgetProviderInfo.spanY = 2;
         launcherAppWidgetProviderInfo.label = widgetLabel;
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 0d9464a..86bbcc1 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -54,7 +54,6 @@
 import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.WidgetCell;
-import com.android.launcher3.widget.WidgetManagerHelper;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 
 import org.junit.Before;
@@ -143,7 +142,6 @@
     }
 
     private List<WidgetItem> generateWidgetItems(String packageName, int numOfWidgets) {
-        WidgetManagerHelper widgetManager = new WidgetManagerHelper(mContext);
         ArrayList<WidgetItem> widgetItems = new ArrayList<>();
         for (int i = 0; i < numOfWidgets; i++) {
             ComponentName cn = ComponentName.createRelative(packageName, ".SampleWidget" + i);
@@ -151,7 +149,7 @@
 
             widgetItems.add(new WidgetItem(
                     LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, widgetInfo),
-                    mTestProfile, mIconCache, mContext, widgetManager));
+                    mTestProfile, mIconCache, mContext));
         }
         return widgetItems;
     }
diff --git a/tests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java b/tests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java
new file mode 100644
index 0000000..b88d12e
--- /dev/null
+++ b/tests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.allapps;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.Flags.FLAG_WORK_SCHEDULER_IN_WORK_PROFILE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.ViewGroup;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.util.ActivityContextWrapper;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WorkUtilityViewTest {
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule =
+            new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+
+    private WorkUtilityView mVut;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = new ActivityContextWrapper(getApplicationContext(),
+                com.android.launcher3.R.style.DynamicColorsBaseLauncherTheme);
+        mVut = (WorkUtilityView) ViewGroup.inflate(context,
+                com.android.launcher3.R.layout.work_mode_utility_view, null);
+    }
+
+    @Test
+    @EnableFlags(FLAG_WORK_SCHEDULER_IN_WORK_PROFILE)
+    public void testInflateFlagOn_visible() {
+        WorkUtilityView workUtilityView = Mockito.spy(mVut);
+        doReturn(true).when(workUtilityView).shouldUseScheduler();
+
+        workUtilityView.onFinishInflate();
+
+        assertThat(workUtilityView.getSchedulerButton().getVisibility()).isEqualTo(VISIBLE);
+        assertThat(workUtilityView.getSchedulerButton().hasOnClickListeners()).isEqualTo(true);
+    }
+
+    @Test
+    @DisableFlags(FLAG_WORK_SCHEDULER_IN_WORK_PROFILE)
+    public void testInflateFlagOff_gone() {
+        mVut.onFinishInflate();
+
+        assertThat(mVut.getSchedulerButton().getVisibility()).isEqualTo(GONE);
+        assertThat(mVut.getSchedulerButton().hasOnClickListeners()).isEqualTo(false);
+    }
+}
diff --git a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
index 34b292c..f494f38 100644
--- a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
+++ b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.compat;
 
+import static android.os.Process.myUserHandle;
+
 import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -136,7 +138,8 @@
     public void testPromiseIcon_addedArchivedApp() throws Throwable {
         installDummyAppAndWaitForUIUpdate();
         assertThat(executeShellCommand(
-                String.format("pm archive %s", DUMMY_PACKAGE)))
+                String.format("pm archive --user %d %s",
+                        myUserHandle().getIdentifier(), DUMMY_PACKAGE)))
                 .isEqualTo("Success\n");
 
         // Create and add test session
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index 882061f..34cbdee 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -2,6 +2,7 @@
 
 import android.appwidget.AppWidgetManager
 import android.content.Intent
+import android.os.Process
 import android.os.UserHandle
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
@@ -136,7 +137,7 @@
     @Test
     fun loadsDataProperly() =
         with(BgDataModel()) {
-            val MAIN_HANDLE = UserHandle.of(0)
+            val MAIN_HANDLE = Process.myUserHandle()
             val mockUserHandles = arrayListOf<UserHandle>(MAIN_HANDLE)
             `when`(userCache.userProfiles).thenReturn(mockUserHandles)
             `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 1))
@@ -186,7 +187,7 @@
     fun setsQuietModeFlagCorrectlyForWorkProfile() =
         with(BgDataModel()) {
             setFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVATE_SPACE)
-            val MAIN_HANDLE = UserHandle.of(0)
+            val MAIN_HANDLE = Process.myUserHandle()
             val mockUserHandles = arrayListOf<UserHandle>(MAIN_HANDLE)
             `when`(userCache.userProfiles).thenReturn(mockUserHandles)
             `when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true)
@@ -215,7 +216,7 @@
     fun setsQuietModeFlagCorrectlyForPrivateProfile() =
         with(BgDataModel()) {
             setFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVATE_SPACE)
-            val MAIN_HANDLE = UserHandle.of(0)
+            val MAIN_HANDLE = Process.myUserHandle()
             val mockUserHandles = arrayListOf<UserHandle>(MAIN_HANDLE)
             `when`(userCache.userProfiles).thenReturn(mockUserHandles)
             `when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true)
diff --git a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
index d9af07a..eec6eed 100644
--- a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
@@ -21,7 +21,7 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.LauncherActivityInfo
 import android.content.pm.LauncherApps
-import android.os.UserHandle
+import android.os.Process.myUserHandle
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -60,7 +60,7 @@
 
     @get:Rule val setFlagsRule = SetFlagsRule()
 
-    private val mUser = UserHandle(0)
+    private val mUser = myUserHandle()
     private val mDataModel: BgDataModel = BgDataModel()
     private val mLauncherModelHelper = LauncherModelHelper()
     private val mContext: SandboxModelContext = spy(mLauncherModelHelper.sandboxContext)
diff --git a/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java b/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java
index 8449853..9fa1500 100644
--- a/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java
+++ b/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.ui;
 
+import static android.os.Process.myUserHandle;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -219,7 +220,8 @@
 
     protected void clearPackageData(String pkg) throws IOException, InterruptedException {
         assertTrue("pm clear command failed",
-                mDevice.executeShellCommand("pm clear " + pkg)
+                mDevice.executeShellCommand(
+                        String.format("pm clear --user %d %s", myUserHandle().getIdentifier(), pkg))
                         .contains("Success"));
         assertTrue("pm wait-for-handler command failed",
                 mDevice.executeShellCommand("pm wait-for-handler")
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index d866a9f..b6c1135 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import android.os.Process;
 import android.util.Log;
 import android.view.View;
 
@@ -73,7 +74,9 @@
 
     @Before
     public void setUp() throws Exception {
-        String output = executeShellCommand("pm create-user --profileOf 0 --managed TestProfile");
+        String output = executeShellCommand(String.format(
+                "pm create-user --profileOf %d --managed TestProfile",
+                Process.myUserHandle().getIdentifier()));
         updateWorkProfileSetupSuccessful("pm create-user", output);
 
         String[] tokens = output.split("\\s+");
@@ -146,7 +149,7 @@
         executeOnLauncher(l -> {
             // Ensure updates are not deferred so notification happens when apps pause.
             l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
-            l.getAppsView().getWorkManager().getWorkUtilityView().performClick();
+            l.getAppsView().getWorkManager().getWorkUtilityView().getWorkFAB().performClick();
         });
 
         waitForLauncherCondition("Work profile toggle OFF failed", launcher -> {
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index bb645d7..64ad305 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -123,7 +123,7 @@
                 v instanceof WidgetCell
                         && v.getTag() instanceof PendingAddWidgetInfo pawi
                         && mWidgetInfo.provider.equals(pawi.componentName)));
-        addToWorkspace(widgetView);
+        addWidgetToWorkspace(widgetView);
 
         // Widget id for which the config activity was opened
         mWidgetId = monitor.getWidgetId();
diff --git a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
index c623513..c852729 100644
--- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
@@ -37,7 +37,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.allapps.AllAppsRecyclerView;
 import com.android.launcher3.celllayout.FavoriteItemsTransaction;
-import com.android.launcher3.icons.ThemedIconDrawable;
+import com.android.launcher3.icons.mono.ThemedIconDrawable;
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.util.BaseLauncherActivityTest;
 import com.android.launcher3.util.Executors;
diff --git a/tests/src/com/android/launcher3/util/BaseLauncherActivityTest.kt b/tests/src/com/android/launcher3/util/BaseLauncherActivityTest.kt
index 61fa7d5..b5702c9 100644
--- a/tests/src/com/android/launcher3/util/BaseLauncherActivityTest.kt
+++ b/tests/src/com/android/launcher3/util/BaseLauncherActivityTest.kt
@@ -170,6 +170,18 @@
         UiDevice.getInstance(getInstrumentation()).waitForIdle()
     }
 
+    /**
+     * Match the behavior with how widget is added in reality with "tap to add" (even with screen
+     * readers).
+     */
+    fun addWidgetToWorkspace(view: View) {
+        executeOnLauncher {
+            view.performClick()
+            UiDevice.getInstance(getInstrumentation()).waitForIdle()
+            view.findViewById<View>(R.id.widget_add_button).performClick()
+        }
+    }
+
     fun ViewGroup.searchView(filter: Predicate<View>): View? {
         if (filter.test(this)) return this
         for (child in children) {
diff --git a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
index 2219003..d093bf7 100644
--- a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
+++ b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
@@ -21,9 +21,9 @@
 
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
+import android.os.Process;
 
 import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.UiDevice;
 
 import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -91,8 +91,9 @@
      * Grants the launcher permission to bind widgets.
      */
     public static ShellCommandRule grantWidgetBind() {
-        return new ShellCommandRule("appwidget grantbind --package "
-                + InstrumentationRegistry.getTargetContext().getPackageName(), null);
+        return new ShellCommandRule(String.format("appwidget grantbind --package %s --user %d",
+                getInstrumentation().getTargetContext().getPackageName(),
+                Process.myUserHandle().getIdentifier()), null);
     }
 
     /**
@@ -109,8 +110,9 @@
     }
 
     public static String getLauncherCommand(ActivityInfo launcher) {
-        return "cmd package set-home-activity " +
-                new ComponentName(launcher.packageName, launcher.name).flattenToString();
+        return String.format("cmd package set-home-activity --user %d %s",
+                Process.myUserHandle().getIdentifier(),
+                new ComponentName(launcher.packageName, launcher.name).flattenToString());
     }
 
     /**
diff --git a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
index 7f6062f..b43bfcf 100644
--- a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
+++ b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
@@ -33,10 +33,9 @@
 import androidx.test.uiautomator.SearchCondition;
 import androidx.test.uiautomator.UiDevice;
 
-import org.junit.Assert;
-
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 
 public class TestHelpers {
 
@@ -113,8 +112,8 @@
     }
 
     private static String checkCrash(Context context, String label, long startTime) {
-        DropBoxManager dropbox = (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE);
-        Assert.assertNotNull("Unable access the DropBoxManager service", dropbox);
+        DropBoxManager dropbox = Objects.requireNonNull(
+                context.getSystemService(DropBoxManager.class));
 
         long timestamp = startTime;
         DropBoxManager.Entry entry;