Merge "Re-enable testOverviewForTablet for persistent taskbar." into udc-dev
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index d7de8a7..14605d8 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -45,7 +45,6 @@
     <uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
     <uses-permission android:name="android.permission.WAKEUP_SURFACE_FLINGER" />
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
-    <uses-permission android:name="android.permission.READ_HOME_APP_SEARCH_DATA" />
 
     <!--
     Permissions required for read/write access to the workspace data. These permission name
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index cebcd42..e041bd5 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -135,7 +135,7 @@
   }
 }
 
-// Next value 48
+// Next value 50
 enum Attribute {
   option allow_alias = true;
 
@@ -190,6 +190,8 @@
   // Result sources
   DATA_SOURCE_APPSEARCH_APP_PREVIEW = 45;
   DATA_SOURCE_APPSEARCH_APP_SRP_PREVIEW = 46;
+  DATA_SOURCE_APPSEARCH_CATEGORY_SRP_PREVIEW = 48;
+  DATA_SOURCE_APPSEARCH_ENTITY_SRP_PREVIEW = 49;
   DATA_SOURCE_AIAI_SEARCH_ROOT = 47;
 
   // Web suggestions provided by AGA
diff --git a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
index 04e87be..4e67629 100644
--- a/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
@@ -59,6 +59,29 @@
             app:layout_constraintStart_toEndOf="@id/thumbnail1"
             app:layout_constraintEnd_toEndOf="parent"/>
 
+        <ImageView
+            android:id="@+id/icon1"
+            android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:layout_marginTop="@dimen/keyboard_quick_switch_taskview_icon_margin"
+            android:layout_marginStart="@dimen/keyboard_quick_switch_taskview_icon_margin"
+            android:importantForAccessibility="no"
+
+            app:layout_constraintTop_toTopOf="@id/thumbnail1"
+            app:layout_constraintStart_toStartOf="@id/thumbnail1"/>
+
+        <ImageView
+            android:id="@+id/icon2"
+            android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:layout_marginTop="@dimen/keyboard_quick_switch_taskview_icon_margin"
+            android:layout_marginStart="@dimen/keyboard_quick_switch_taskview_icon_margin"
+            android:importantForAccessibility="no"
+            android:visibility="gone"
+
+            app:layout_constraintTop_toTopOf="@id/thumbnail2"
+            app:layout_constraintStart_toStartOf="@id/thumbnail2"/>
+
     </androidx.constraintlayout.widget.ConstraintLayout>
 
 </com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
diff --git a/quickstep/res/layout/keyboard_quick_switch_overview.xml b/quickstep/res/layout/keyboard_quick_switch_overview.xml
index 062a9c9..e7b1f23 100644
--- a/quickstep/res/layout/keyboard_quick_switch_overview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_overview.xml
@@ -41,8 +41,8 @@
             android:layout_height="@dimen/keyboard_quick_switch_recents_icon_size"
             android:layout_marginBottom="8dp"
             android:src="@drawable/ic_empty_recents"
+            android:tint="?androidprv:attr/materialColorOnSurface"
 
-            app:tint="?androidprv:attr/materialColorOnSurface"
             app:layout_constraintVertical_chainStyle="packed"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintBottom_toTopOf="@id/text"
@@ -50,7 +50,7 @@
             app:layout_constraintEnd_toEndOf="parent"/>
 
         <TextView
-            style="@style/KeyboardQuickSwitchOverview"
+            style="@style/KeyboardQuickSwitchText"
             android:id="@+id/text"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
diff --git a/quickstep/res/layout/keyboard_quick_switch_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
index 691df6e..4d213fa 100644
--- a/quickstep/res/layout/keyboard_quick_switch_taskview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_taskview.xml
@@ -59,6 +59,29 @@
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintEnd_toEndOf="parent"/>
 
+        <ImageView
+            android:id="@+id/icon1"
+            android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:layout_marginTop="@dimen/keyboard_quick_switch_taskview_icon_margin"
+            android:layout_marginStart="@dimen/keyboard_quick_switch_taskview_icon_margin"
+            android:importantForAccessibility="no"
+
+            app:layout_constraintTop_toTopOf="@id/thumbnail1"
+            app:layout_constraintStart_toStartOf="@id/thumbnail1"/>
+
+        <ImageView
+            android:id="@+id/icon2"
+            android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
+            android:layout_marginTop="@dimen/keyboard_quick_switch_taskview_icon_margin"
+            android:layout_marginStart="@dimen/keyboard_quick_switch_taskview_icon_margin"
+            android:importantForAccessibility="no"
+            android:visibility="gone"
+
+            app:layout_constraintTop_toTopOf="@id/thumbnail2"
+            app:layout_constraintStart_toStartOf="@id/thumbnail2"/>
+
     </androidx.constraintlayout.widget.ConstraintLayout>
 
 </com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
diff --git a/quickstep/res/layout/keyboard_quick_switch_thumbnail.xml b/quickstep/res/layout/keyboard_quick_switch_thumbnail.xml
index cd6587c..dde9cac 100644
--- a/quickstep/res/layout/keyboard_quick_switch_thumbnail.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_thumbnail.xml
@@ -19,4 +19,5 @@
     android:layout_height="match_parent"
     android:scaleType="centerCrop"
     android:background="@drawable/keyboard_quick_switch_task_view_background"
-    android:clipToOutline="true"/>
+    android:clipToOutline="true"
+    android:importantForAccessibility="no"/>
diff --git a/quickstep/res/layout/keyboard_quick_switch_view.xml b/quickstep/res/layout/keyboard_quick_switch_view.xml
index 58c0c40..16abdee 100644
--- a/quickstep/res/layout/keyboard_quick_switch_view.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_view.xml
@@ -15,6 +15,7 @@
 -->
 <com.android.launcher3.taskbar.KeyboardQuickSwitchView
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
@@ -27,6 +28,43 @@
     android:focusableInTouchMode="true"
     app:layout_ignoreInsets="true">
 
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/no_recent_items_pane"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
+        android:paddingVertical="@dimen/keyboard_quick_switch_view_spacing"
+        android:alpha="0"
+        android:visibility="gone">
+
+        <ImageView
+            android:id="@+id/no_recent_items_icon"
+            android:layout_width="@dimen/keyboard_quick_switch_no_recent_items_icon_size"
+            android:layout_height="@dimen/keyboard_quick_switch_no_recent_items_icon_size"
+            android:layout_marginBottom="@dimen/keyboard_quick_switch_no_recent_items_icon_margin"
+            android:src="@drawable/ic_empty_recents"
+            android:tint="?androidprv:attr/materialColorOnSurfaceInverse"
+            android:importantForAccessibility="no"
+
+            app:layout_constraintVertical_chainStyle="packed"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toTopOf="@id/no_recent_items_text"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+        <TextView
+            style="@style/KeyboardQuickSwitchText.OnBackground"
+            android:id="@+id/no_recent_items_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/recents_empty_message"
+
+            app:layout_constraintTop_toBottomOf="@id/no_recent_items_icon"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
     <HorizontalScrollView
         android:id="@+id/scroll_view"
         android:layout_width="wrap_content"
@@ -34,7 +72,7 @@
         android:fillViewport="true"
         android:scrollbars="none"
         android:alpha="0"
-        android:visibility="invisible"
+        android:visibility="gone"
 
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 8e11a2d..9d36c67 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -98,7 +98,7 @@
     <string name="accessibility_rotate_button" msgid="4771825231336502943">"Ротирајте го екранот"</string>
     <string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"Обука за лентата со задачи"</string>
     <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Повлечете апликација настрана за да користите 2 апликации"</string>
-    <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Полека повлечете нагоре за да се прикаже „Лентата со задачи“"</string>
+    <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Полека повлечете нагоре за да се прикаже лентата со задачи"</string>
     <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Добивајте предлози за апликации според вашата рутина"</string>
     <string name="taskbar_edu_settings_persistent" msgid="1387372982791296151">"Вклучете навигација со движење во „Поставки“ за автоматско сокривање на „Лентата со задачи“"</string>
     <string name="taskbar_edu_features" msgid="3320337287472848162">"Правете повеќе со една лента со задачи"</string>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index d69b155..bb4f74d 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -372,6 +372,8 @@
     <dimen name="keyboard_quick_switch_border_width">4dp</dimen>
     <dimen name="keyboard_quick_switch_taskview_width">104dp</dimen>
     <dimen name="keyboard_quick_switch_taskview_height">134dp</dimen>
+    <dimen name="keyboard_quick_switch_taskview_icon_size">28dp</dimen>
+    <dimen name="keyboard_quick_switch_taskview_icon_margin">4dp</dimen>
     <dimen name="keyboard_quick_switch_recents_icon_size">20dp</dimen>
     <dimen name="keyboard_quick_switch_margin_top">56dp</dimen>
     <dimen name="keyboard_quick_switch_margin_ends">16dp</dimen>
@@ -379,4 +381,6 @@
     <dimen name="keyboard_quick_switch_split_view_spacing">2dp</dimen>
     <dimen name="keyboard_quick_switch_view_radius">28dp</dimen>
     <dimen name="keyboard_quick_switch_task_view_radius">16dp</dimen>
+    <dimen name="keyboard_quick_switch_no_recent_items_icon_size">24dp</dimen>
+    <dimen name="keyboard_quick_switch_no_recent_items_icon_margin">8dp</dimen>
 </resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 2b6f749..2d8c45a 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -295,6 +295,7 @@
             =1{Show # more app.}
             other{Show # more apps.}
         }</string>
+
     <!-- Accessibility label for quick switch tiles showing split tasks [CHAR LIMIT=NONE] -->
     <string name="quick_switch_split_task"><xliff:g id="app_name_1" example="Chrome">%1$s</xliff:g> and <xliff:g id="app_name_2" example="Gmail">%2$s</xliff:g></string>
 </resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index e1afb26..ead5343 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -215,13 +215,17 @@
         <item name="android:textSize">14sp</item>
     </style>
 
-    <style name="KeyboardQuickSwitchOverview">
+    <style name="KeyboardQuickSwitchText">
         <item name="fontFamily">google-sans-text</item>
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
         <item name="lineHeight">20sp</item>
     </style>
 
+    <style name="KeyboardQuickSwitchText.OnBackground" parent="KeyboardQuickSwitchText">
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceInverse</item>
+    </style>
+
     <style name="GestureTutorialActivity"
         parent="@style/AppTheme">
         <item name="background">@android:color/transparent</item>
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index c4962cd..7f655cf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -129,8 +129,8 @@
      */
     int launchFocusedTask() {
         // Return -1 so that the RecentsView is not incorrectly opened when the user closes the
-        // quick switch view by tapping the screen.
-        return mQuickSwitchViewController == null
+        // quick switch view by tapping the screen or when there are no recent tasks.
+        return mQuickSwitchViewController == null || mTasks.isEmpty()
                 ? -1 : mQuickSwitchViewController.launchFocusedTask();
     }
 
@@ -181,7 +181,7 @@
             mModel.getThumbnailCache().updateThumbnailInBackground(task, callback);
         }
 
-        void updateTitleInBackground(Task task, Consumer<Task> callback) {
+        void updateIconInBackground(Task task, Consumer<Task> callback) {
             mModel.getIconCache().updateIconInBackground(task, callback);
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
index 926ede1..08857b7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
@@ -20,6 +20,7 @@
 import android.animation.Animator;
 import android.content.Context;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.util.AttributeSet;
@@ -46,6 +47,8 @@
 
     @Nullable private ImageView mThumbnailView1;
     @Nullable private ImageView mThumbnailView2;
+    @Nullable private ImageView mIcon1;
+    @Nullable private ImageView mIcon2;
     @Nullable private View mContent;
 
     public KeyboardQuickSwitchTaskView(@NonNull Context context) {
@@ -67,6 +70,9 @@
             int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        TypedArray ta = context.obtainStyledAttributes(
+                attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
+
         setWillNotDraw(false);
         Resources resources = context.getResources();
         mBorderAnimator = new BorderAnimator(
@@ -75,17 +81,8 @@
                         R.dimen.keyboard_quick_switch_border_width),
                 /* borderRadiusPx= */ resources.getDimensionPixelSize(
                         R.dimen.keyboard_quick_switch_task_view_radius),
-                /* borderColor= */ attrs == null
-                        ? DEFAULT_BORDER_COLOR
-                        : context.getTheme()
-                                .obtainStyledAttributes(
-                                        attrs,
-                                        R.styleable.TaskView,
-                                        defStyleAttr,
-                                        defStyleRes)
-                                .getColor(
-                                        R.styleable.TaskView_borderColor,
-                                        DEFAULT_BORDER_COLOR),
+                /* borderColor= */ ta.getColor(
+                        R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR),
                 /* invalidateViewCallback= */ KeyboardQuickSwitchTaskView.this::invalidate,
                 /* viewScaleTargetProvider= */ new BorderAnimator.ViewScaleTargetProvider() {
                     @NonNull
@@ -100,14 +97,16 @@
                         return mContent;
                     }
                 });
+        ta.recycle();
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-
         mThumbnailView1 = findViewById(R.id.thumbnail1);
         mThumbnailView2 = findViewById(R.id.thumbnail2);
+        mIcon1 = findViewById(R.id.icon1);
+        mIcon2 = findViewById(R.id.icon2);
         mContent = findViewById(R.id.content);
     }
 
@@ -126,11 +125,13 @@
             @NonNull Task task1,
             @Nullable Task task2,
             @Nullable ThumbnailUpdateFunction thumbnailUpdateFunction,
-            @Nullable TitleUpdateFunction titleUpdateFunction) {
+            @Nullable IconUpdateFunction iconUpdateFunction) {
         applyThumbnail(mThumbnailView1, task1, thumbnailUpdateFunction);
         applyThumbnail(mThumbnailView2, task2, thumbnailUpdateFunction);
 
-        if (titleUpdateFunction == null) {
+        if (iconUpdateFunction == null) {
+            applyIcon(mIcon1, task1);
+            applyIcon(mIcon2, task2);
             setContentDescription(task2 == null
                     ? task1.titleDescription
                     : getContext().getString(
@@ -139,16 +140,23 @@
                             task2.titleDescription));
             return;
         }
-        titleUpdateFunction.updateTitleInBackground(task1, t ->
-                setContentDescription(task1.titleDescription));
+        iconUpdateFunction.updateIconInBackground(task1, t -> {
+            applyIcon(mIcon1, task1);
+            if (task2 != null) {
+                return;
+            }
+            setContentDescription(task1.titleDescription);
+        });
         if (task2 == null) {
             return;
         }
-        titleUpdateFunction.updateTitleInBackground(task2, t ->
-                setContentDescription(getContext().getString(
-                        R.string.quick_switch_split_task,
-                        task1.titleDescription,
-                        task2.titleDescription)));
+        iconUpdateFunction.updateIconInBackground(task2, t -> {
+            applyIcon(mIcon2, task2);
+            setContentDescription(getContext().getString(
+                    R.string.quick_switch_split_task,
+                    task1.titleDescription,
+                    task2.titleDescription));
+        });
     }
 
     private void applyThumbnail(
@@ -177,13 +185,21 @@
         thumbnailView.setImageBitmap(bm);
     }
 
+    private void applyIcon(@Nullable ImageView iconView, @Nullable Task task) {
+        if (iconView == null || task == null) {
+            return;
+        }
+        iconView.setVisibility(VISIBLE);
+        iconView.setImageDrawable(task.icon);
+    }
+
     protected interface ThumbnailUpdateFunction {
 
         void updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback);
     }
 
-    protected interface TitleUpdateFunction {
+    protected interface IconUpdateFunction {
 
-        void updateTitleInBackground(Task task, Consumer<Task> callback);
+        void updateIconInBackground(Task task, Consumer<Task> callback);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 745defc..2cdfb18 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -85,6 +85,8 @@
     private final AnimatedFloat mOutlineAnimationProgress = new AnimatedFloat(
             this::invalidateOutline);
 
+    private boolean mDisplayingRecentTasks;
+    private View mNoRecentItemsPane;
     private HorizontalScrollView mScrollView;
     private ConstraintLayout mContent;
 
@@ -119,6 +121,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mNoRecentItemsPane = findViewById(R.id.no_recent_items_pane);
         mScrollView = findViewById(R.id.scroll_view);
         mContent = findViewById(R.id.content);
 
@@ -145,20 +148,20 @@
         taskView.setOnClickListener(v -> mViewCallbacks.launchTappedTask(index));
 
         LayoutParams lp = new LayoutParams(width, mTaskViewHeight);
-        // Create a right-to-left ordering of views (or left-to-right in RTL locales)
+        // Create a left-to-right ordering of views (or right-to-left in RTL locales)
         if (previousView != null) {
-            lp.endToStart = previousView.getId();
+            lp.startToEnd = previousView.getId();
         } else {
-            lp.endToEnd = PARENT_ID;
+            lp.startToStart = PARENT_ID;
         }
         lp.topToTop = PARENT_ID;
         lp.bottomToBottom = PARENT_ID;
         // Add spacing between views
-        lp.setMarginEnd(mSpacing);
+        lp.setMarginStart(mSpacing);
         if (isFinalView) {
-            // Add spacing to the start of the final view so that scrolling ends with some padding.
-            lp.startToStart = PARENT_ID;
-            lp.setMarginStart(mSpacing);
+            // Add spacing to the end of the final view so that scrolling ends with some padding.
+            lp.endToEnd = PARENT_ID;
+            lp.setMarginEnd(mSpacing);
             lp.horizontalBias = 1f;
         }
 
@@ -167,7 +170,7 @@
                 groupTask.task1,
                 groupTask.task2,
                 updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
-                updateTasks ? mViewCallbacks::updateTitleInBackground : null);
+                updateTasks ? mViewCallbacks::updateIconInBackground : null);
 
         mContent.addView(taskView, lp);
         return taskView;
@@ -187,8 +190,8 @@
 
         ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
                 width, mTaskViewHeight);
-        lp.startToStart = PARENT_ID;
-        lp.endToStart = previousView.getId();
+        lp.endToEnd = PARENT_ID;
+        lp.startToEnd = previousView.getId();
         lp.topToTop = PARENT_ID;
         lp.bottomToBottom = PARENT_ID;
         lp.setMarginEnd(mSpacing);
@@ -204,10 +207,6 @@
             boolean updateTasks,
             int currentFocusIndexOverride,
             @NonNull KeyboardQuickSwitchViewController.ViewCallbacks viewCallbacks) {
-        if (groupTasks.isEmpty()) {
-            // Do not show the quick switch view.
-            return;
-        }
         mViewCallbacks = viewCallbacks;
         Resources resources = context.getResources();
         int width = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_taskview_width);
@@ -237,6 +236,7 @@
                             resources.getString(R.string.quick_switch_overflow),
                             Locale.getDefault()).format(args));
         }
+        mDisplayingRecentTasks = !groupTasks.isEmpty();
 
         getViewTreeObserver().addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
@@ -262,13 +262,16 @@
         alphaAnimation.setDuration(ALPHA_ANIMATION_DURATION_MS);
         closeAnimation.play(alphaAnimation);
 
+        View displayedContent = mDisplayingRecentTasks ? mScrollView : mNoRecentItemsPane;
         Animator translationYAnimation = ObjectAnimator.ofFloat(
-                mScrollView, TRANSLATION_Y, 0, -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP));
+                displayedContent,
+                TRANSLATION_Y,
+                0, -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP));
         translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
         translationYAnimation.setInterpolator(CLOSE_TRANSLATION_Y_INTERPOLATOR);
         closeAnimation.play(translationYAnimation);
 
-        Animator contentAlphaAnimation = ObjectAnimator.ofFloat(mScrollView, ALPHA, 1f, 0f);
+        Animator contentAlphaAnimation = ObjectAnimator.ofFloat(displayedContent, ALPHA, 1f, 0f);
         contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
         closeAnimation.play(contentAlphaAnimation);
 
@@ -300,19 +303,24 @@
         alphaAnimation.setDuration(ALPHA_ANIMATION_DURATION_MS);
         mOpenAnimation.play(alphaAnimation);
 
+        View displayedContent = mDisplayingRecentTasks ? mScrollView : mNoRecentItemsPane;
         Animator translationXAnimation = ObjectAnimator.ofFloat(
-                mScrollView, TRANSLATION_X, -Utilities.dpToPx(CONTENT_START_TRANSLATION_X_DP), 0);
+                displayedContent,
+                TRANSLATION_X,
+                -Utilities.dpToPx(CONTENT_START_TRANSLATION_X_DP), 0);
         translationXAnimation.setDuration(CONTENT_TRANSLATION_X_ANIMATION_DURATION_MS);
         translationXAnimation.setInterpolator(OPEN_TRANSLATION_X_INTERPOLATOR);
         mOpenAnimation.play(translationXAnimation);
 
         Animator translationYAnimation = ObjectAnimator.ofFloat(
-                mScrollView, TRANSLATION_Y, -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP), 0);
+                displayedContent,
+                TRANSLATION_Y,
+                -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP), 0);
         translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
         translationYAnimation.setInterpolator(OPEN_TRANSLATION_Y_INTERPOLATOR);
         mOpenAnimation.play(translationYAnimation);
 
-        Animator contentAlphaAnimation = ObjectAnimator.ofFloat(mScrollView, ALPHA, 0f, 1f);
+        Animator contentAlphaAnimation = ObjectAnimator.ofFloat(displayedContent, ALPHA, 0f, 1f);
         contentAlphaAnimation.setStartDelay(CONTENT_ALPHA_ANIMATION_START_DELAY_MS);
         contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
         mOpenAnimation.play(contentAlphaAnimation);
@@ -353,7 +361,7 @@
                 } else {
                     animateFocusMove(-1, currentFocusIndexOverride);
                 }
-                mScrollView.setVisibility(VISIBLE);
+                displayedContent.setVisibility(VISIBLE);
                 setVisibility(VISIBLE);
                 requestFocus();
             }
@@ -372,6 +380,9 @@
     }
 
     protected void animateFocusMove(int fromIndex, int toIndex) {
+        if (!mDisplayingRecentTasks) {
+            return;
+        }
         KeyboardQuickSwitchTaskView focusedTask = getTaskAt(toIndex);
         if (focusedTask == null) {
             return;
@@ -402,16 +413,16 @@
                 } else if (toIndex > fromIndex || toIndex == 0) {
                     // Scrolling to next task view
                     if (mIsRtl) {
-                        scrollRightTo(focusedTask);
-                    } else {
                         scrollLeftTo(focusedTask);
+                    } else {
+                        scrollRightTo(focusedTask);
                     }
                 } else {
                     // Scrolling to previous task view
                     if (mIsRtl) {
-                        scrollLeftTo(focusedTask);
-                    } else {
                         scrollRightTo(focusedTask);
+                    } else {
+                        scrollLeftTo(focusedTask);
                     }
                 }
                 if (mViewCallbacks != null) {
@@ -425,11 +436,15 @@
 
     @Override
     public boolean onKeyUp(int keyCode, KeyEvent event) {
-        return (mViewCallbacks != null && mViewCallbacks.onKeyUp(keyCode, event, mIsRtl))
+        return (mViewCallbacks != null
+                && mViewCallbacks.onKeyUp(keyCode, event, mIsRtl, mDisplayingRecentTasks))
                 || super.onKeyUp(keyCode, event);
     }
 
     private void initializeScroll(int index, boolean shouldTruncateTarget) {
+        if (!mDisplayingRecentTasks) {
+            return;
+        }
         View task = getTaskAt(index);
         if (task == null) {
             return;
@@ -449,6 +464,9 @@
 
     private void scrollRightTo(
             @NonNull View targetTask, boolean shouldTruncateTarget, boolean smoothScroll) {
+        if (!mDisplayingRecentTasks) {
+            return;
+        }
         if (smoothScroll && !shouldScroll(targetTask, shouldTruncateTarget)) {
             return;
         }
@@ -468,6 +486,9 @@
 
     private void scrollLeftTo(
             @NonNull View targetTask, boolean shouldTruncateTarget, boolean smoothScroll) {
+        if (!mDisplayingRecentTasks) {
+            return;
+        }
         if (smoothScroll && !shouldScroll(targetTask, shouldTruncateTarget)) {
             return;
         }
@@ -491,7 +512,7 @@
 
     @Nullable
     protected KeyboardQuickSwitchTaskView getTaskAt(int index) {
-        return index < 0 || index >= mContent.getChildCount()
+        return !mDisplayingRecentTasks || index < 0 || index >= mContent.getChildCount()
                 ? null : (KeyboardQuickSwitchTaskView) mContent.getChildAt(index);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index c1f764f..7bd8898 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -169,7 +169,7 @@
 
     class ViewCallbacks {
 
-        boolean onKeyUp(int keyCode, KeyEvent event, boolean isRTL) {
+        boolean onKeyUp(int keyCode, KeyEvent event, boolean isRTL, boolean allowTraversal) {
             if (keyCode != KeyEvent.KEYCODE_TAB
                     && keyCode != KeyEvent.KEYCODE_DPAD_RIGHT
                     && keyCode != KeyEvent.KEYCODE_DPAD_LEFT
@@ -181,6 +181,9 @@
                 closeQuickSwitchView(true);
                 return true;
             }
+            if (!allowTraversal) {
+                return false;
+            }
             boolean traverseBackwards = (keyCode == KeyEvent.KEYCODE_TAB && event.isShiftPressed())
                     || (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && !isRTL)
                     || (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && isRTL);
@@ -195,6 +198,9 @@
                             // focus a less recent app or loop back to the opposite end
                             : ((mCurrentFocusIndex + 1) % taskCount));
 
+            if (mCurrentFocusIndex == toIndex) {
+                return true;
+            }
             mKeyboardQuickSwitchView.animateFocusMove(mCurrentFocusIndex, toIndex);
 
             return true;
@@ -213,8 +219,8 @@
             mControllerCallbacks.updateThumbnailInBackground(task, callback);
         }
 
-        void updateTitleInBackground(Task task, Consumer<Task> callback) {
-            mControllerCallbacks.updateTitleInBackground(task, callback);
+        void updateIconInBackground(Task task, Consumer<Task> callback) {
+            mControllerCallbacks.updateIconInBackground(task, callback);
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index cf82900..a1c9f05 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
@@ -43,6 +44,7 @@
 import android.content.pm.ActivityInfo.Config;
 import android.content.pm.LauncherApps;
 import android.content.res.Resources;
+import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
@@ -95,10 +97,13 @@
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
+import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
 import com.android.launcher3.util.TraceHelper;
@@ -567,6 +572,22 @@
         }
     }
 
+    @Override
+    public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
+        RunnableList callbacks = new RunnableList();
+        ActivityOptions options = ActivityOptions.makeCustomAnimation(
+                this, 0, 0, Color.TRANSPARENT,
+                Executors.MAIN_EXECUTOR.getHandler(), null,
+                elapsedRealTime -> callbacks.executeAllAndDestroy());
+        options.setSplashScreenStyle(splashScreenStyle);
+        return new ActivityOptionsWrapper(options, callbacks);
+    }
+
+    @Override
+    public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
+        return makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED);
+    }
+
     /**
      * Sets a new data-source for this taskbar instance
      */
@@ -979,11 +1000,16 @@
         mControllers.taskbarEduTooltipController.hide();
     }
 
-    /** Returns {@code true} if taskbar All Apps is open. */
+    /** Returns {@code true} if Taskbar All Apps is open. */
     public boolean isTaskbarAllAppsOpen() {
         return mControllers.taskbarAllAppsController.isOpen();
     }
 
+    /** Toggles the Taskbar's stash state. */
+    public void toggleTaskbarStash() {
+        mControllers.taskbarStashController.toggleTaskbarStash();
+    }
+
     /**
      * Called to start the taskbar translation spring to its settled translation (0).
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 88fea31..0a83279 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -36,6 +36,7 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
+import android.util.Log;
 import android.util.Pair;
 import android.view.DragEvent;
 import android.view.MotionEvent;
@@ -87,6 +88,7 @@
  */
 public class TaskbarDragController extends DragController<BaseTaskbarContext> implements
         TaskbarControllers.LoggableTaskbarController {
+    private static final String TAG = "TaskbarDragController";
 
     private static final boolean DEBUG_DRAG_SHADOW_SURFACE = false;
     private static final int ANIM_DURATION_RETURN_ICON_TO_TASKBAR = 300;
@@ -318,12 +320,26 @@
             @Override
             public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
                 int iconSize = Math.max(mDragIconSize, btv.getWidth());
-                shadowSize.set(iconSize, iconSize);
+                if (iconSize > 0) {
+                    shadowSize.set(iconSize, iconSize);
+                } else {
+                    Log.d(TAG, "Invalid icon size, dragSize=" + mDragIconSize
+                            + " viewWidth=" + btv.getWidth());
+                }
+
                 // The registration point was taken before the icon scaled to mDragIconSize, so
                 // offset the registration to where the touch is on the new size.
                 int offsetX = (mDragIconSize - mDragObject.dragView.getDragRegionWidth()) / 2;
                 int offsetY = (mDragIconSize - mDragObject.dragView.getDragRegionHeight()) / 2;
-                shadowTouchPoint.set(mRegistrationX + offsetX, mRegistrationY + offsetY);
+                int touchX = mRegistrationX + offsetX;
+                int touchY = mRegistrationY + offsetY;
+                if (touchX >= 0 && touchY >= 0) {
+                    shadowTouchPoint.set(touchX, touchY);
+                } else {
+                    Log.d(TAG, "Invalid touch point, "
+                            + "registrationXY=(" + mRegistrationX + ", " + mRegistrationY + ") "
+                            + "offsetXY=(" + offsetX + ", " + offsetY + ")");
+                }
             }
 
             @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index dfbd5bb..008f5f6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -474,8 +474,7 @@
                     public void onAnimationEnd(Animator animation) {
                         TaskbarStashController stashController =
                                 mControllers.taskbarStashController;
-                        stashController.updateAndAnimateTransientTaskbar(
-                                /* stash */ true, /* duration */ 0);
+                        stashController.updateAndAnimateTransientTaskbar(/* stash */ true);
                     }
                 });
             } else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 1727dd3..6f82c7d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -501,16 +501,9 @@
     }
 
     /**
-     * Stash or unstashes the transient taskbar, using the default TASKBAR_STASH_DURATION.
-     */
-    public void updateAndAnimateTransientTaskbar(boolean stash) {
-        updateAndAnimateTransientTaskbar(stash, TASKBAR_STASH_DURATION);
-    }
-
-    /**
      * Stash or unstashes the transient taskbar.
      */
-    public void updateAndAnimateTransientTaskbar(boolean stash, long duration) {
+    public void updateAndAnimateTransientTaskbar(boolean stash) {
         if (!DisplayController.isTransientTaskbar(mActivity)) {
             return;
         }
@@ -575,6 +568,12 @@
         return false;
     }
 
+    /** Toggles the Taskbar's stash state. */
+    public void toggleTaskbarStash() {
+        if (!DisplayController.isTransientTaskbar(mActivity) || !hasAnyFlag(FLAGS_IN_APP)) return;
+        updateAndAnimateTransientTaskbar(!hasAnyFlag(FLAG_STASHED_IN_APP_AUTO));
+    }
+
     /**
      * Adds the Taskbar unstash to Hotseat animator to the animator set.
      *
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 65f449c..79a301a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -68,6 +68,7 @@
 import android.content.IntentSender;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
+import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.hardware.SensorManager;
@@ -143,6 +144,7 @@
 import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.ObjectWrapper;
@@ -343,14 +345,16 @@
     }
 
     @Override
-    public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
+    public RunnableList startActivitySafely(View v, Intent intent, ItemInfo item) {
         // Only pause is taskbar controller is not present
         mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
-        boolean started = super.startActivitySafely(v, intent, item);
-        if (getTaskbarUIController() == null && !started) {
+        RunnableList result = super.startActivitySafely(v, intent, item);
+        if (getTaskbarUIController() == null && result == null) {
             mHotseatPredictionController.setPauseUIUpdate(false);
+        } else {
+            result.add(() -> mHotseatPredictionController.setPauseUIUpdate(false));
         }
-        return started;
+        return result;
     }
 
     @Override
@@ -370,11 +374,6 @@
                 | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) {
             onStateOrResumeChanging((getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0);
         }
-
-        if (((changeBits & ACTIVITY_STATE_STARTED) != 0
-                || (changeBits & getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) {
-            mHotseatPredictionController.setPauseUIUpdate(false);
-        }
     }
 
     @Override
@@ -1102,6 +1101,17 @@
     }
 
     @Override
+    public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
+        RunnableList callbacks = new RunnableList();
+        ActivityOptions options = ActivityOptions.makeCustomAnimation(
+                this, 0, 0, Color.TRANSPARENT,
+                Executors.MAIN_EXECUTOR.getHandler(), null,
+                elapsedRealTime -> callbacks.executeAllAndDestroy());
+        options.setSplashScreenStyle(splashScreenStyle);
+        return new ActivityOptionsWrapper(options, callbacks);
+    }
+
+    @Override
     @BinderThread
     public void enterStageSplitFromRunningApp(boolean leftOrTop) {
         mSplitWithKeyboardShortcutController.enterStageSplit(leftOrTop);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 40dfd82..8cbd6e8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -41,11 +41,9 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.TaskUtils;
-import com.android.quickstep.TopTaskTracker;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.OverviewToHomeAnim;
 import com.android.quickstep.views.RecentsView;
@@ -108,11 +106,6 @@
         if (AbstractFloatingView.getTopOpenViewWithType(mLauncher, typeToClose) != null) {
             return true;
         }
-        if (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
-                && TopTaskTracker.INSTANCE.get(mLauncher).getCachedTopTask(false)
-                        .isExcludedAssistant()) {
-            return true;
-        }
         return false;
     }
 
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index c8c6292..4c4b9b4 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -236,6 +236,9 @@
                     homeIsOnTop = true;
                 }
             }
+            if (activityInterface.allowAllAppsFromOverview()) {
+                homeIsOnTop = true;
+            }
             if (!homeIsOnTop) {
                 options.setTransientLaunch();
             }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 99a57a2..1fbfbe6 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,7 +24,6 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE;
-import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TRACKPAD_GESTURE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
@@ -85,6 +84,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.StatefulActivity;
@@ -207,7 +207,15 @@
         @BinderThread
         @Override
         public void onTaskbarToggled() {
-            // To be implemented.
+            if (!FeatureFlags.ENABLE_KEYBOARD_TASKBAR_TOGGLE.get()) return;
+            MAIN_EXECUTOR.execute(() -> {
+                TaskbarActivityContext activityContext =
+                        mTaskbarManager.getCurrentActivityContext();
+
+                if (activityContext != null) {
+                    activityContext.toggleTaskbarStash();
+                }
+            });
         }
 
         @BinderThread
@@ -1089,22 +1097,17 @@
         boolean hasWindowFocus = activity.getRootView().hasWindowFocus();
         boolean isPreviousGestureAnimatingToLauncher =
                 previousGestureState.isRunningAnimationToLauncher();
-        boolean forcingOverviewInputConsumer =
-                ASSISTANT_GIVES_LAUNCHER_FOCUS.get() && forceOverviewInputConsumer;
         boolean isInLiveTileMode = gestureState.getActivityInterface().isInLiveTileMode();
         reasonString.append(SUBSTRING_PREFIX)
                 .append(hasWindowFocus
                         ? "activity has window focus"
                         : (isPreviousGestureAnimatingToLauncher
                                 ? "previous gesture is still animating to launcher"
-                                : (forcingOverviewInputConsumer
-                                        ? "assistant gives launcher focus and forcing focus"
-                                        : (isInLiveTileMode
-                                                ? "device is in live mode"
-                                                : "all overview focus conditions failed"))));
+                                : isInLiveTileMode
+                                        ? "device is in live mode"
+                                        : "all overview focus conditions failed"));
         if (hasWindowFocus
                 || isPreviousGestureAnimatingToLauncher
-                || forcingOverviewInputConsumer
                 || isInLiveTileMode) {
             reasonString.append(SUBSTRING_PREFIX)
                     .append("overview should have focus, using OverviewInputConsumer");
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 2008129..4dbf4e3 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -19,6 +19,7 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
+import static com.android.launcher3.LauncherState.EDIT_MODE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
@@ -133,7 +134,8 @@
 
     @Override
     public void onStateTransitionComplete(LauncherState finalState) {
-        if (finalState == NORMAL || finalState == SPRING_LOADED || finalState == ALL_APPS) {
+        if (finalState == NORMAL || finalState == SPRING_LOADED  || finalState == EDIT_MODE
+                || finalState == ALL_APPS) {
             // Clean-up logic that occurs when recents is no longer in use/visible.
             reset();
         }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index d570708..c47c946 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -45,6 +45,7 @@
 import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -438,6 +439,9 @@
 
         setWillNotDraw(!keyboardFocusHighlightEnabled);
 
+        TypedArray ta = context.obtainStyledAttributes(
+                attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
+
         mBorderAnimator = !keyboardFocusHighlightEnabled
                 ? null
                 : new BorderAnimator(
@@ -445,18 +449,10 @@
                         /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
                                 R.dimen.keyboard_quick_switch_border_width),
                         /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
-                        /* borderColor= */ attrs == null
-                        ? DEFAULT_BORDER_COLOR
-                        : context.getTheme()
-                                .obtainStyledAttributes(
-                                        attrs,
-                                        R.styleable.TaskView,
-                                        defStyleAttr,
-                                        defStyleRes)
-                                .getColor(
-                                        R.styleable.TaskView_borderColor,
-                                        DEFAULT_BORDER_COLOR),
+                        /* borderColor= */ ta.getColor(
+                                R.styleable.TaskView_borderColor, DEFAULT_BORDER_COLOR),
                         /* invalidateViewCallback= */ TaskView.this::invalidate);
+        ta.recycle();
     }
 
     protected void updateBorderBounds(Rect bounds) {
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 62d46d3..97e34c5 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -62,6 +62,7 @@
 import com.android.launcher3.util.rule.SamplerRule;
 import com.android.launcher3.util.rule.ScreenRecordRule;
 import com.android.launcher3.util.rule.TestStabilityRule;
+import com.android.launcher3.util.rule.ViewCaptureRule;
 import com.android.quickstep.views.RecentsView;
 
 import org.junit.After;
@@ -115,10 +116,12 @@
             Utilities.enableRunningInTestHarnessForTests();
         }
 
+        final ViewCaptureRule viewCaptureRule = new ViewCaptureRule();
         mOrderSensitiveRules = RuleChain
                 .outerRule(new SamplerRule())
                 .around(new NavigationModeSwitchRule(mLauncher))
-                .around(new FailureWatcher(mDevice, mLauncher));
+                .around(viewCaptureRule)
+                .around(new FailureWatcher(mDevice, mLauncher, viewCaptureRule.getViewCapture()));
 
         mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
                 getHomeIntentInPackage(context),
diff --git a/res/color-night-v31/folder_background_dark.xml b/res/color-night-v31/folder_background_dark.xml
index 696e8ea..f415210 100644
--- a/res/color-night-v31/folder_background_dark.xml
+++ b/res/color-night-v31/folder_background_dark.xml
@@ -15,6 +15,6 @@
 -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android" >
     <item
-        android:color="@android:color/system_neutral2_500"
-        android:lStar="35" />
+        android:color="@android:color/system_neutral2_900"
+        android:lStar="12" />
 </selector>
diff --git a/res/color-night-v31/folder_preview_dark.xml b/res/color-night-v31/folder_preview_dark.xml
index bdd48a2..644d61a 100644
--- a/res/color-night-v31/folder_preview_dark.xml
+++ b/res/color-night-v31/folder_preview_dark.xml
@@ -15,6 +15,6 @@
 -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android" >
     <item
-        android:color="@android:color/system_neutral2_500"
-        android:lStar="30" />
+        android:color="@android:color/system_neutral1_900"
+        android:lStar="17" />
 </selector>
diff --git a/res/color-v31/folder_background_light.xml b/res/color-v31/folder_background_light.xml
index eb2fdd7..4dd088b 100644
--- a/res/color-v31/folder_background_light.xml
+++ b/res/color-v31/folder_background_light.xml
@@ -15,6 +15,6 @@
 -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android" >
     <item
-        android:color="@android:color/system_neutral1_500"
-        android:lStar="98" />
+        android:color="@android:color/system_neutral1_50"
+        android:lStar="94" />
 </selector>
diff --git a/res/color-v31/folder_preview_light.xml b/res/color-v31/folder_preview_light.xml
index ed1205e..6727b24 100644
--- a/res/color-v31/folder_preview_light.xml
+++ b/res/color-v31/folder_preview_light.xml
@@ -14,7 +14,5 @@
      limitations under the License.
 -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android" >
-    <item
-        android:color="@android:color/system_accent2_500"
-        android:lStar="80" />
+    <item android:color="@android:color/system_accent2_200"/>
 </selector>
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 5518dc8..31f4870 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -51,7 +51,7 @@
             android:singleLine="true"
             android:textColor="?attr/folderTextColor"
             android:textColorHighlight="?android:attr/colorControlHighlight"
-            android:textColorHint="?attr/folderHintColor"/>
+            android:textColorHint="?attr/folderHintTextColor"/>
 
         <com.android.launcher3.pageindicators.PageIndicatorDots
             android:id="@+id/folder_page_indicator"
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 1b211f1..4d617c5 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -50,8 +50,7 @@
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Pertsonalak"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Lanekoak"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Elkarrizketak"</string>
-    <!-- no translation found for widget_category_note_taking (3469689394504266039) -->
-    <skip />
+    <string name="widget_category_note_taking" msgid="3469689394504266039">"Oharrak idazteko"</string>
     <string name="widget_education_header" msgid="4874760613775913787">"Informazio erabilgarria beti eskura"</string>
     <string name="widget_education_content" msgid="1731667670753497052">"Aplikaziorik ireki beharrik gabe informazioa zuzenean jasotzeko, gehitu widgetak hasierako pantailan"</string>
     <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Sakatu hau widgeten ezarpenak aldatzeko"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index de60f3a..c2ee822 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -83,7 +83,7 @@
     <string name="permlab_write_settings" msgid="4820028712156303762">"да пишува поставки и кратенки на почетна страница"</string>
     <string name="permdesc_write_settings" msgid="726859348127868466">"Овозможува апликацијата да ги менува поставките и кратенките на почетната страница."</string>
     <string name="gadget_error_text" msgid="740356548025791839">"Не може да се вчита виџетот"</string>
-    <string name="gadget_setup_text" msgid="8348374825537681407">"Поставки за виџет"</string>
+    <string name="gadget_setup_text" msgid="8348374825537681407">"Поставки за виџетот"</string>
     <string name="gadget_complete_setup_text" msgid="309040266978007925">"Допрете за да го завршите поставувањето"</string>
     <string name="uninstall_system_app_text" msgid="4172046090762920660">"Ова е системска апликација и не може да се деинсталира."</string>
     <string name="folder_hint_text" msgid="5174843001373488816">"Изменете го името"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 1d4b989..b4515a0 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -91,7 +91,7 @@
     <string name="dotted_app_label" msgid="1865617679843363410">"{count,plural, =1{{app_name} संबंधित # सूचना आहे}other{{app_name} संबंधित # सूचना आहेत}}"</string>
     <string name="default_scroll_format" msgid="7475544710230993317">"%2$d पैकी %1$d पेज"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$d पैकी %1$d मुख्य स्क्रीन"</string>
-    <string name="workspace_new_page" msgid="257366611030256142">"नवीन मुख्य स्क्रीन पेज"</string>
+    <string name="workspace_new_page" msgid="257366611030256142">"नवीन होम स्क्रीन पेज"</string>
     <string name="folder_opened" msgid="94695026776264709">"फोल्डर उघडले, <xliff:g id="WIDTH">%1$d</xliff:g> बाय <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"फोल्डर बंद करण्यासाठी टॅप करा"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"पुनर्नामित करणे सेव्ह करण्यासाठी टॅप करा"</string>
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index 0c036ff..9f09b99 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -34,8 +34,10 @@
     <color name="workspace_text_color_light">@android:color/system_neutral1_0</color>
     <color name="workspace_text_color_dark">@android:color/system_neutral1_1000</color>
 
-    <color name="folder_hint_text_color_light">@android:color/system_neutral1_50</color>
-    <color name="folder_hint_text_color_dark">@android:color/system_neutral2_700</color>
+    <color name="folder_text_color_light">@android:color/system_neutral1_900</color>
+    <color name="folder_text_color_dark">@android:color/system_neutral1_100</color>
+    <color name="folder_hint_text_color_light">@android:color/system_neutral2_700</color>
+    <color name="folder_hint_text_color_dark">@android:color/system_neutral2_200</color>
 
     <color name="text_color_primary_dark">@android:color/system_neutral1_50</color>
     <color name="text_color_secondary_dark">@android:color/system_neutral2_200</color>
@@ -44,7 +46,7 @@
     <color name="wallpaper_popup_scrim">@android:color/system_neutral1_900</color>
 
     <color name="folder_pagination_color_light">@android:color/system_accent1_600</color>
-    <color name="folder_pagination_color_dark">@android:color/system_accent2_100</color>
+    <color name="folder_pagination_color_dark">@android:color/system_accent1_200</color>
 
     <color name="home_settings_header_accent">@android:color/system_accent1_600</color>
     <color name="home_settings_header_collapsed">@android:color/system_neutral1_100</color>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 418f5a7..0772557 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -48,7 +48,7 @@
     <attr name="folderIconRadius" format="float" />
     <attr name="folderIconBorderColor" format="color" />
     <attr name="folderTextColor" format="color" />
-    <attr name="folderHintColor" format="color" />
+    <attr name="folderHintTextColor" format="color" />
     <attr name="isFolderDarkText" format="boolean" />
     <attr name="workspaceAccentColor" format="color" />
     <attr name="dropTargetHoverTextColor" format="color" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index b1bff18..2295043 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -60,17 +60,19 @@
     <color name="workspace_text_color_light">#FFF</color>
     <color name="workspace_text_color_dark">#FF000000</color>
 
-    <color name="folder_hint_text_color_light">#FFF</color>
-    <color name="folder_hint_text_color_dark">#FF000000</color>
+    <color name="folder_text_color_light">#1F1F1F</color>
+    <color name="folder_text_color_dark">#E3E3E3</color>
+    <color name="folder_hint_text_color_light">#444746</color>
+    <color name="folder_hint_text_color_dark">#C4C7C5</color>
 
-    <color name="folder_background_light">#F9F9F9</color>
-    <color name="folder_background_dark">#464746</color>
+    <color name="folder_background_light">#EFEDED</color>
+    <color name="folder_background_dark">#1F2020</color>
 
-    <color name="folder_preview_light">#F9F9F9</color>
-    <color name="folder_preview_dark">#464746</color>
+    <color name="folder_preview_light">#7FCFFF</color>
+    <color name="folder_preview_dark">#2A2A2A</color>
 
-    <color name="folder_pagination_color_light">#ff006c5f</color>
-    <color name="folder_pagination_color_dark">#ffbfebe3</color>
+    <color name="folder_pagination_color_light">#0B57D0</color>
+    <color name="folder_pagination_color_dark">#A8C7FA</color>
 
     <color name="text_color_primary_dark">#FFFFFFFF</color>
     <color name="text_color_secondary_dark">#FFFFFFFF</color>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8f75550..d0f2067 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -54,9 +54,9 @@
         <item name="folderPreviewColor">@color/folder_preview_light</item>
         <item name="folderBackgroundColor">@color/folder_background_light</item>
         <item name="folderIconBorderColor">?android:attr/colorPrimary</item>
-        <item name="folderTextColor">?android:attr/textColorPrimary</item>
         <item name="isFolderDarkText">true</item>
-        <item name="folderHintColor">@color/folder_hint_text_color_dark</item>
+        <item name="folderTextColor">@color/folder_text_color_light</item>
+        <item name="folderHintTextColor">@color/folder_hint_text_color_light</item>
         <item name="loadingIconColor">#CCFFFFFF</item>
         <item name="iconOnlyShortcutColor">?android:attr/textColorSecondary</item>
         <item name="eduHalfSheetBGColor">?android:attr/colorAccent</item>
@@ -116,7 +116,8 @@
         <item name="folderBackgroundColor">@color/folder_background_dark</item>
         <item name="folderIconBorderColor">?android:attr/colorPrimary</item>
         <item name="isFolderDarkText">false</item>
-        <item name="folderHintColor">@color/folder_hint_text_color_light</item>
+        <item name="folderTextColor">@color/folder_text_color_dark</item>
+        <item name="folderHintTextColor">@color/folder_hint_text_color_dark</item>
         <item name="isMainColorDark">true</item>
         <item name="loadingIconColor">#99FFFFFF</item>
         <item name="iconOnlyShortcutColor">#B3FFFFFF</item>
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 45b03c2..8876a1b 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -156,6 +156,13 @@
     }
 
     @Override
+    public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
+        ActivityOptionsWrapper wrapper = super.makeDefaultActivityOptions(splashScreenStyle);
+        addOnResumeCallback(wrapper.onEndCallback::executeAllAndDestroy);
+        return wrapper;
+    }
+
+    @Override
     protected void onStart() {
         super.onStart();
 
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 5163ede..6763eaf 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -18,6 +18,7 @@
 
 import static android.animation.ValueAnimator.areAnimatorsEnabled;
 
+import static com.android.launcher3.LauncherState.EDIT_MODE;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
 import static com.android.launcher3.dragndrop.DraggableView.DRAGGABLE_ICON;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
@@ -571,7 +572,9 @@
     }
 
     protected void updateBgAlpha() {
-        mBackground.setAlpha((int) (mSpringLoadedProgress * 255));
+        if (!getWorkspace().mLauncher.isInState(EDIT_MODE)) {
+            mBackground.setAlpha((int) (mSpringLoadedProgress * 255));
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 0231090..fb41044 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -55,7 +55,10 @@
 import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.ResourceHelper;
 import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.workspace.CalculatedWorkspaceSpec;
+import com.android.launcher3.workspace.WorkspaceSpecs;
 
 import java.io.PrintWriter;
 import java.util.Locale;
@@ -101,9 +104,14 @@
     public final float aspectRatio;
 
     public final boolean isScalableGrid;
-    public final boolean isResponsiveGrid;
     private final int mTypeIndex;
 
+    // Responsive grid
+    private final boolean mIsResponsiveGrid;
+    private WorkspaceSpecs mWorkspaceSpecs;
+    private CalculatedWorkspaceSpec mResponsiveWidthSpec;
+    private CalculatedWorkspaceSpec mResponsiveHeightSpec;
+
     /**
      * The maximum amount of left/right workspace padding as a percentage of the screen width.
      * To be clear, this means that up to 7% of the screen width can be used as left padding, and
@@ -294,9 +302,8 @@
         this.rotationHint = windowBounds.rotationHint;
         mInsets.set(windowBounds.insets);
 
-        // TODO(b/241386436):
-        //  for testing that the flag works only, shouldn't change any launcher behaviour
-        isResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE;
+        // TODO(b/241386436): shouldn't change any launcher behaviour
+        mIsResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE;
 
         isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
         // Determine device posture.
@@ -335,6 +342,14 @@
             }
         }
 
+        if (mIsResponsiveGrid) {
+            mWorkspaceSpecs = new WorkspaceSpecs(new ResourceHelper(context, inv.workspaceSpecsId));
+            mResponsiveWidthSpec = mWorkspaceSpecs.getCalculatedWidthSpec(inv.numColumns,
+                    availableWidthPx);
+            mResponsiveHeightSpec = mWorkspaceSpecs.getCalculatedHeightSpec(inv.numRows,
+                    availableHeightPx);
+        }
+
         if (DisplayController.isTransientTaskbar(context)) {
             float invTransientIconSizeDp = inv.transientTaskbarIconSize[mTypeIndex];
             taskbarIconSize = pxFromDp(invTransientIconSizeDp, mMetrics);
@@ -1582,7 +1597,7 @@
 
         writer.println(prefix + "\taspectRatio:" + aspectRatio);
 
-        writer.println(prefix + "\tisResponsiveGrid:" + isResponsiveGrid);
+        writer.println(prefix + "\tisResponsiveGrid:" + mIsResponsiveGrid);
         writer.println(prefix + "\tisScalableGrid:" + isScalableGrid);
 
         writer.println(prefix + "\tinv.numRows: " + inv.numRows);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ffd56cc..0b75c45 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -34,6 +34,7 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.EDIT_MODE;
 import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
 import static com.android.launcher3.LauncherState.NORMAL;
@@ -44,6 +45,7 @@
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
 import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
+import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
 import static com.android.launcher3.config.FeatureFlags.SHOW_DOT_PAGINATION;
 import static com.android.launcher3.logging.StatsLogManager.EventEnum;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
@@ -901,12 +903,8 @@
 
         final int pendingAddWidgetId = requestArgs.getWidgetId();
 
-        Runnable exitSpringLoaded = new Runnable() {
-            @Override
-            public void run() {
-                mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
-            }
-        };
+        Runnable exitSpringLoaded = MULTI_SELECT_EDIT_MODE.get() ? null
+                : () -> mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
 
         if (requestCode == REQUEST_BIND_APPWIDGET) {
             // This is called only if the user did not previously have permissions to bind widgets
@@ -1040,10 +1038,9 @@
             final AppWidgetHostView layout = mAppWidgetHolder.createView(this, appWidgetId,
                     requestArgs.getWidgetHandler().getProviderInfo(this));
             boundWidget = layout;
-            onCompleteRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    completeAddAppWidget(appWidgetId, requestArgs, layout, null);
+            onCompleteRunnable = () -> {
+                completeAddAppWidget(appWidgetId, requestArgs, layout, null);
+                if (!isInState(EDIT_MODE)) {
                     mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
                 }
             };
@@ -1178,7 +1175,7 @@
         }
         addActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
 
-        if (state == SPRING_LOADED) {
+        if (state == SPRING_LOADED || state == EDIT_MODE) {
             // Prevent any Un/InstallShortcutReceivers from updating the db while we are
             // not on homescreen
             ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_DRAG_AND_DROP);
@@ -1532,7 +1529,8 @@
                 mStateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
                     @Override
                     public void onStateTransitionComplete(LauncherState finalState) {
-                        if (mPrevLauncherState == SPRING_LOADED && finalState == NORMAL) {
+                        if ((mPrevLauncherState == SPRING_LOADED || mPrevLauncherState == EDIT_MODE)
+                                && finalState == NORMAL) {
                             AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout);
                             mStateManager.removeStateListener(this);
                         }
@@ -1900,13 +1898,9 @@
                 REQUEST_CREATE_APPWIDGET)) {
             // If the configuration flow was not started, add the widget
 
-            Runnable onComplete = new Runnable() {
-                @Override
-                public void run() {
-                    // Exit spring loaded mode if necessary after adding the widget
-                    mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
-                }
-            };
+            // Exit spring loaded mode if necessary after adding the widget
+            Runnable onComplete = MULTI_SELECT_EDIT_MODE.get() ? null
+                    : () -> mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
             completeAddAppWidget(appWidgetId, info, boundWidget,
                     addFlowHandler.getProviderInfo(this));
             mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
@@ -2154,30 +2148,38 @@
     }
 
     @Override
-    public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
+    public RunnableList startActivitySafely(View v, Intent intent, ItemInfo item) {
         if (!hasBeenResumed()) {
+            RunnableList result = new RunnableList();
             // Workaround an issue where the WM launch animation is clobbered when finishing the
             // recents animation into launcher. Defer launching the activity until Launcher is
             // next resumed.
-            addOnResumeCallback(() -> startActivitySafely(v, intent, item));
+            addOnResumeCallback(() -> {
+                RunnableList actualResult = startActivitySafely(v, intent, item);
+                if (actualResult != null) {
+                    actualResult.add(result::executeAllAndDestroy);
+                } else {
+                    result.executeAllAndDestroy();
+                }
+            });
             if (mOnDeferredActivityLaunchCallback != null) {
                 mOnDeferredActivityLaunchCallback.run();
                 mOnDeferredActivityLaunchCallback = null;
             }
-            return true;
+            return result;
         }
 
-        boolean success = super.startActivitySafely(v, intent, item);
-        if (success && v instanceof BubbleTextView) {
+        RunnableList result = super.startActivitySafely(v, intent, item);
+        if (result != null && v instanceof BubbleTextView) {
             // This is set to the view that launched the activity that navigated the user away
             // from launcher. Since there is no callback for when the activity has finished
             // launching, enable the press state and keep this reference to reset the press
             // state when we return to launcher.
             BubbleTextView btv = (BubbleTextView) v;
             btv.setStayPressed(true);
-            addOnResumeCallback(() -> btv.setStayPressed(false));
+            result.add(() -> btv.setStayPressed(false));
         }
-        return success;
+        return result;
     }
 
     boolean isHotseatLayout(View layout) {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 65870d3..617afcb 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -92,6 +92,15 @@
 
     static final String TAG = "Launcher.Model";
 
+    // Broadcast intent to track when the profile gets locked:
+    // ACTION_MANAGED_PROFILE_UNAVAILABLE can be used until Android U where profile no longer gets
+    // locked when paused.
+    // ACTION_PROFILE_INACCESSIBLE always means that the profile is getting locked but it only
+    // appeared in Android S.
+    private static final String ACTION_PROFILE_LOCKED = Utilities.ATLEAST_U
+            ? Intent.ACTION_PROFILE_INACCESSIBLE
+            : Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
+
     @NonNull
     private final LauncherAppState mApp;
     @NonNull
@@ -290,9 +299,9 @@
             // If we have changed locale we need to clear out the labels in all apps/workspace.
             forceReload();
         } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
-                || Intent.ACTION_PROFILE_INACCESSIBLE.equals(action)
                 || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)
-                || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
+                || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)
+                || Intent.ACTION_PROFILE_INACCESSIBLE.equals(action)) {
             UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
             if (TestProtocol.sDebugTracing) {
                 Log.d(TestProtocol.WORK_TAB_MISSING, "onBroadcastIntent intentAction: " + action +
@@ -305,9 +314,7 @@
                             PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
                 }
 
-                // ACTION_PROFILE_INACCESSIBLE sends the profile back to locked mode, so
-                // we need to run the state change task again.
-                if (Intent.ACTION_PROFILE_INACCESSIBLE.equals(action)
+                if (ACTION_PROFILE_LOCKED.equals(action)
                         || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) {
                     enqueueModelUpdateTask(new UserLockStateChangedTask(
                             user, Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)));
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index b8d13ed..8b124dc 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
 import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.testing.shared.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.EDIT_MODE_STATE_ORDINAL;
 import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_ORDINAL;
 import static com.android.launcher3.testing.shared.TestProtocol.HINT_STATE_TWO_BUTTON_ORDINAL;
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
@@ -38,6 +39,7 @@
 
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.states.EditModeState;
 import com.android.launcher3.states.HintState;
 import com.android.launcher3.states.SpringLoadedState;
 import com.android.launcher3.testing.shared.TestProtocol;
@@ -103,7 +105,7 @@
                 }
             };
 
-    private static final LauncherState[] sAllStates = new LauncherState[10];
+    private static final LauncherState[] sAllStates = new LauncherState[11];
 
     /**
      * TODO: Create a separate class for NORMAL state.
@@ -123,6 +125,7 @@
      */
     public static final LauncherState SPRING_LOADED = new SpringLoadedState(
             SPRING_LOADED_STATE_ORDINAL);
+    public static final LauncherState EDIT_MODE = new EditModeState(EDIT_MODE_STATE_ORDINAL);
     public static final LauncherState ALL_APPS = new AllAppsState(ALL_APPS_STATE_ORDINAL);
     public static final LauncherState HINT_STATE = new HintState(HINT_STATE_ORDINAL);
     public static final LauncherState HINT_STATE_TWO_BUTTON = new HintState(
@@ -328,7 +331,9 @@
      * Gets the translation provider for workspace pages.
      */
     public PageTranslationProvider getWorkspacePageTranslationProvider(Launcher launcher) {
-        if (this != SPRING_LOADED || !launcher.getDeviceProfile().isTwoPanels) {
+        if (this != SPRING_LOADED
+                || this != EDIT_MODE
+                || !launcher.getDeviceProfile().isTwoPanels) {
             return DEFAULT_PAGE_TRANSLATION_PROVIDER;
         }
         final float quarterPageSpacing = launcher.getWorkspace().getPageSpacing() / 4f;
@@ -343,6 +348,16 @@
         };
     }
 
+    /**
+     * Called when leaving this LauncherState
+     * @param launcher - Launcher instance
+     * @param toState - New LauncherState that is being entered
+     */
+    public void onLeavingState(Launcher launcher, LauncherState toState) {
+        // no-op
+        // override to handle when leaving current LauncherState
+    }
+
     @Override
     public LauncherState getHistoryForState(LauncherState previousState) {
         // No history is supported
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 2fa1a57..73bb828 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.EDIT_MODE;
 import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
 import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED;
 import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_INACCESSIBLE;
@@ -494,8 +495,9 @@
             }
         }
 
-        // Always enter the spring loaded mode
-        mLauncher.getStateManager().goToState(SPRING_LOADED);
+        if (!mLauncher.isInState(EDIT_MODE)) {
+            mLauncher.getStateManager().goToState(SPRING_LOADED);
+        }
         mStatsLogManager.logger().withItemInfo(dragObject.dragInfo)
                 .withInstanceId(dragObject.logInstanceId)
                 .log(LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED);
@@ -1432,7 +1434,8 @@
     }
 
     private boolean workspaceInScrollableState() {
-        return mLauncher.isInState(SPRING_LOADED) || !workspaceInModalState();
+        return mLauncher.isInState(SPRING_LOADED) || mLauncher.isInState(EDIT_MODE)
+                || !workspaceInModalState();
     }
 
     /**
@@ -1526,6 +1529,7 @@
     @Override
     public void setState(LauncherState toState) {
         onStartStateTransition();
+        mLauncher.getStateManager().getState().onLeavingState(mLauncher, toState);
         mStateTransitionAnimation.setState(toState);
         onEndStateTransition();
     }
@@ -1537,6 +1541,7 @@
     public void setStateWithAnimation(
             LauncherState toState, StateAnimationConfig config, PendingAnimation animation) {
         StateTransitionListener listener = new StateTransitionListener();
+        mLauncher.getStateManager().getState().onLeavingState(mLauncher, toState);
         mStateTransitionAnimation.setStateWithAnimation(toState, config, animation);
 
         // Invalidate the pages now, so that we have the visible pages before the
@@ -1999,7 +2004,9 @@
                         distance, false, d)
                         || addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
                         distance, d, false)) {
-                    mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+                    if (!mLauncher.isInState(EDIT_MODE)) {
+                        mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+                    }
                     return;
                 }
 
@@ -2128,14 +2135,19 @@
                     // spring-loaded mode so the page meets the icon where it was picked up.
                     final RunnableList callbackList = new RunnableList();
                     final Runnable onCompleteCallback = onCompleteRunnable;
+                    LauncherState currentState = mLauncher.getStateManager().getState();
                     mLauncher.getDragController().animateDragViewToOriginalPosition(
                             /* onComplete= */ callbackList::executeAllAndDestroy, cell,
-                            SPRING_LOADED.getTransitionDuration(mLauncher, true /* isToState */));
-                    mLauncher.getStateManager().goToState(NORMAL, /* delay= */ 0,
-                            onCompleteCallback == null
-                                    ? null
-                                    : forSuccessCallback(
-                                            () -> callbackList.add(onCompleteCallback)));
+                            currentState.getTransitionDuration(mLauncher, true /* isToState */));
+                    if (!mLauncher.isInState(EDIT_MODE)) {
+                        mLauncher.getStateManager().goToState(NORMAL, /* delay= */ 0,
+                                onCompleteCallback == null
+                                        ? null
+                                        : forSuccessCallback(
+                                                () -> callbackList.add(onCompleteCallback)));
+                    } else if (onCompleteCallback != null) {
+                        forSuccessCallback(() -> callbackList.add(onCompleteCallback));
+                    }
                     mLauncher.getDropTargetBar().onDragEnd();
                     parent.onDropChild(cell);
                     return;
@@ -2159,8 +2171,12 @@
             }
             parent.onDropChild(cell);
 
-            mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY,
-                    onCompleteRunnable == null ? null : forSuccessCallback(onCompleteRunnable));
+            if (!mLauncher.isInState(EDIT_MODE)) {
+                mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY,
+                        onCompleteRunnable == null ? null : forSuccessCallback(onCompleteRunnable));
+            } else if (onCompleteRunnable != null) {
+                forSuccessCallback(onCompleteRunnable);
+            }
             mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
                     .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
         }
@@ -2734,7 +2750,8 @@
         final int screenId = getIdForScreen(cellLayout);
         if (!mLauncher.isHotseatLayout(cellLayout)
                 && screenId != getScreenIdForPageIndex(mCurrentPage)
-                && !mLauncher.isInState(SPRING_LOADED)) {
+                && !mLauncher.isInState(SPRING_LOADED)
+                && !mLauncher.isInState(EDIT_MODE)) {
             snapToPage(getPageIndexForScreenId(screenId));
         }
 
@@ -2812,7 +2829,6 @@
         } else {
             // This is for other drag/drop cases, like dragging from All Apps
             mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
-
             View view;
 
             switch (info.itemType) {
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 55ab7f1..565d7da 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -59,6 +59,7 @@
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.graphics.Scrim;
 import com.android.launcher3.graphics.SysUiScrim;
+import com.android.launcher3.states.EditModeState;
 import com.android.launcher3.states.SpringLoadedState;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.DynamicResource;
@@ -212,8 +213,8 @@
             PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter,
             StateAnimationConfig config) {
         float pageAlpha = pageAlphaProvider.getPageAlpha(childIndex);
-        float springLoadedProgress = (state instanceof SpringLoadedState) ? 1.0f : 0f;
-
+        float springLoadedProgress =
+                (state instanceof  SpringLoadedState || state instanceof EditModeState) ? 1f : 0f;
         propertySetter.setFloat(cl,
                 CellLayout.SPRING_LOADED_PROGRESS, springLoadedProgress, ZOOM_OUT);
         Interpolator fadeInterpolator = config.getInterpolator(ANIM_WORKSPACE_FADE,
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index a671c6e..30af502 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -45,7 +45,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.testing.shared.TestProtocol;
@@ -53,7 +52,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.function.Predicate;
 
@@ -97,15 +95,7 @@
             StatsLogManager statsLogManager) {
         mUserManager = userManager;
         mAllApps = allApps;
-        boolean cloningChanges = FeatureFlags.ENABLE_APP_CLONING_CHANGES_IN_LAUNCHER.get();
-        if (TestProtocol.sDebugTracing) {
-            Log.d(WORK_TAB_MISSING, "matcher flag: " + cloningChanges);
-        }
-        if (cloningChanges) {
-            mMatcher = ofWorkProfileUser(userManager);
-        } else {
-            mMatcher = mAllApps.mPersonalMatcher.negate();
-        }
+        mMatcher = mAllApps.mPersonalMatcher.negate();
         mStatsLogManager = statsLogManager;
     }
 
@@ -280,27 +270,4 @@
             }
         };
     }
-
-    /**
-     * Filter to only display apps in managed profile in work tab.
-     */
-    private Predicate<ItemInfo> ofWorkProfileUser(UserManager um) {
-        return info -> info != null && isManagedProfile(um, info.user.hashCode());
-    }
-
-
-    private static boolean isManagedProfile(UserManager um, int userId) {
-        try {
-            // isManagedProfile is a @SystemApi.
-            String methodName = "isManagedProfile";
-            Method method = um.getClass().getDeclaredMethod(methodName, int.class);
-            Object result = method.invoke(um, userId);
-            if (result instanceof Boolean) {
-                return (boolean) result;
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to call #isManagedProfile via reflection from Launcher");
-        }
-        return false;
-    }
 }
diff --git a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
index 714304b..64fd237 100644
--- a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
+++ b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java
@@ -62,7 +62,8 @@
         if (mHighlightedView instanceof BubbleTextView
                 && mHighlightedView.getTag() instanceof ItemInfo) {
             ItemInfo itemInfo = (ItemInfo) mHighlightedView.getTag();
-            return mLauncher.startActivitySafely(mHighlightedView, itemInfo.getIntent(), itemInfo);
+            return mLauncher.startActivitySafely(
+                    mHighlightedView, itemInfo.getIntent(), itemInfo) != null;
         }
         return false;
     }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 2891ce1..621c2ab 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -86,7 +86,7 @@
             "ENABLE_ONE_SEARCH_MOTION", ENABLED, "Enables animations in OneSearch.");
 
     public static final BooleanFlag ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES = getReleaseFlag(
-            270394041, "ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", TEAMFOOD,
+            270394041, "ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", DISABLED,
             "Enable option to replace decorator-based search result backgrounds with drawables");
 
     public static final BooleanFlag ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION = getReleaseFlag(
@@ -142,10 +142,6 @@
             "Enables haptics opening/closing All apps");
 
     // TODO(Block 6): Clean up flags
-    public static final BooleanFlag WIDGETS_IN_LAUNCHER_PREVIEW = getDebugFlag(270393268,
-            "WIDGETS_IN_LAUNCHER_PREVIEW", ENABLED,
-            "Enables widgets in Launcher preview for the Wallpaper app.");
-
     public static final BooleanFlag ENABLE_ALL_APPS_SEARCH_IN_TASKBAR = getDebugFlag(270393900,
             "ENABLE_ALL_APPS_SEARCH_IN_TASKBAR", DISABLED,
             "Enables Search box in Taskbar All Apps.");
@@ -211,25 +207,12 @@
             "Allows on device search in all apps logging");
 
     // TODO(Block 14): Cleanup flags
-    public static final BooleanFlag ASSISTANT_GIVES_LAUNCHER_FOCUS = getDebugFlag(270391641,
-            "ASSISTANT_GIVES_LAUNCHER_FOCUS", DISABLED,
-            "Allow Launcher to handle nav bar gestures while Assistant is running over it");
-
     public static final BooleanFlag NOTIFY_CRASHES = getDebugFlag(270393108, "NOTIFY_CRASHES",
             DISABLED, "Sends a notification whenever launcher encounters an uncaught exception.");
 
-    public static final BooleanFlag FORCE_PERSISTENT_TASKBAR = getDebugFlag(270395077,
-            "FORCE_PERSISTENT_TASKBAR", DISABLED, "Forces taskbar to be persistent, even in gesture"
-                    + " nav mode and when transient taskbar is enabled.");
-
     public static final BooleanFlag ENABLE_TRANSIENT_TASKBAR = getDebugFlag(270395798,
             "ENABLE_TRANSIENT_TASKBAR", ENABLED, "Enables transient taskbar.");
 
-    // TODO(Block 15): Clean up flags
-    public static final BooleanFlag ENABLE_APP_CLONING_CHANGES_IN_LAUNCHER = getDebugFlag(266177840,
-            "ENABLE_APP_CLONING_CHANGES_IN_LAUNCHER", DISABLED,
-            "Removes clone apps from the work profile tab.");
-
     // TODO(Block 16): Clean up flags
     // When enabled the promise icon is visible in all apps while installation an app.
     public static final BooleanFlag PROMISE_APPS_IN_ALL_APPS = getDebugFlag(270390012,
@@ -314,6 +297,10 @@
             "Enables receiving unfold animation events from sysui instead of calculating "
                     + "them in launcher process using hinge sensor values.");
 
+    public static final BooleanFlag ENABLE_WIDGET_TRANSITION_FOR_RESIZING = getDebugFlag(268553314,
+            "ENABLE_WIDGET_TRANSITION_FOR_RESIZING", DISABLED,
+            "Enable widget transition animation when resizing the widgets");
+
     public static final BooleanFlag PREEMPTIVE_UNFOLD_ANIMATION_START = getDebugFlag(270397209,
             "PREEMPTIVE_UNFOLD_ANIMATION_START", ENABLED,
             "Enables starting the unfold animation preemptively when unfolding, without"
@@ -404,6 +391,10 @@
     public static final BooleanFlag ENABLE_KEYBOARD_QUICK_SWITCH = getDebugFlag(270396844,
             "ENABLE_KEYBOARD_QUICK_SWITCH", ENABLED, "Enables keyboard quick switching");
 
+    public static final BooleanFlag ENABLE_KEYBOARD_TASKBAR_TOGGLE = getDebugFlag(281726846,
+            "ENABLE_KEYBOARD_TASKBAR_TOGGLE", ENABLED,
+            "Enables keyboard taskbar stash toggling");
+
     // TODO(Block 30): Clean up flags
     public static final BooleanFlag USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES = getDebugFlag(270395010,
             "USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES", DISABLED,
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java
index aaa5b1a..da6f446 100644
--- a/src/com/android/launcher3/dragndrop/LauncherDragController.java
+++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherState.EDIT_MODE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
@@ -157,7 +158,9 @@
 
     @Override
     protected void exitDrag() {
-        mActivity.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+        if (!mActivity.isInState(EDIT_MODE)) {
+            mActivity.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragView.java b/src/com/android/launcher3/dragndrop/LauncherDragView.java
index cc68e2e..4c43865 100644
--- a/src/com/android/launcher3/dragndrop/LauncherDragView.java
+++ b/src/com/android/launcher3/dragndrop/LauncherDragView.java
@@ -57,7 +57,8 @@
     @Override
     public void onStateTransitionComplete(LauncherState finalState) {
         setVisibility((finalState == LauncherState.NORMAL
-                || finalState == LauncherState.SPRING_LOADED) ? VISIBLE : INVISIBLE);
+                || finalState == LauncherState.SPRING_LOADED
+                || finalState == LauncherState.EDIT_MODE) ? VISIBLE : INVISIBLE);
     }
 
     @Override
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index dd74125..7bdec1c 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -16,6 +16,11 @@
 
 package com.android.launcher3.dragndrop;
 
+import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherState.EDIT_MODE;
+import static com.android.launcher3.LauncherState.SPRING_LOADED;
+import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
+
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.ComponentName;
@@ -29,10 +34,8 @@
 import android.os.Process;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -90,11 +93,11 @@
 
     @Override
     public WorkspaceItemInfo createWorkspaceItemInfo() {
+        long transitionDuration = (MULTI_SELECT_EDIT_MODE.get() ? EDIT_MODE : SPRING_LOADED)
+                .getTransitionDuration(Launcher.getLauncher(mContext), true /* isToState */);
         // Total duration for the drop animation to complete.
         long duration = mContext.getResources().getInteger(R.integer.config_dropAnimMaxDuration) +
-                LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY +
-                LauncherState.SPRING_LOADED.getTransitionDuration(Launcher.getLauncher(mContext),
-                        true /* isToState */);
+                SPRING_LOADED_EXIT_DELAY + transitionDuration;
         // Delay the actual accept() call until the drop animation is complete.
         return PinRequestHelper.createWorkspaceItemFromPinItemRequest(
                 mContext, mRequestSupplier.get(), duration);
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 3c31b7a..4ae54e6 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -19,6 +19,7 @@
 import static android.text.TextUtils.isEmpty;
 
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherState.EDIT_MODE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
@@ -558,7 +559,7 @@
                 .inflate(R.layout.user_folder_icon_normalized, null);
     }
 
-    private void startAnimation(final AnimatorSet a) {
+    private void addAnimationStartListeners(AnimatorSet a) {
         mLauncherDelegate.forEachVisibleWorkspacePage(
                 visiblePage -> addAnimatorListenerForPage(a, (CellLayout) visiblePage));
 
@@ -574,7 +575,6 @@
                 mCurrentAnimator = null;
             }
         });
-        a.start();
     }
 
     private void addAnimatorListenerForPage(AnimatorSet a, CellLayout currentCellLayout) {
@@ -734,10 +734,14 @@
 
         mPageIndicator.stopAllAnimations();
 
+        // b/282158620 because setCurrentPlayTime() below will start animator, we need to register
+        // {@link AnimatorListener} before it so that {@link AnimatorListener#onAnimationStart} can
+        // be called to register mCurrentAnimator, which will be used to cancel animator
+        addAnimationStartListeners(anim);
         // Because t=0 has the folder match the folder icon, we can skip the
         // first frame and have the same movement one frame earlier.
         anim.setCurrentPlayTime(Math.min(getSingleFrameMs(getContext()), anim.getTotalDuration()));
-        startAnimation(anim);
+        anim.start();
 
         // Make sure the folder picks up the last drag move even if the finger doesn't move.
         if (mDragController.isDragging()) {
@@ -815,7 +819,8 @@
                 mIsAnimatingClosed = false;
             }
         });
-        startAnimation(a);
+        addAnimationStartListeners(a);
+        a.start();
     }
 
     @Override
@@ -1339,7 +1344,10 @@
                     mLauncherDelegate.getModelWriter());
         }
 
-        launcher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+        if (!launcher.isInState(EDIT_MODE)) {
+            launcher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
+        }
+
         if (d.stateAnnouncer != null) {
             d.stateAnnouncer.completeAction(R.string.item_moved);
         }
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 05ad57a..2ce6c78 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -265,23 +265,37 @@
         Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0);
         play(a, z, mIsOpening ? midDuration : 0, midDuration);
 
-        // Store clip variables
-        CellLayout cellLayout = mContent.getCurrentCellLayout();
-        boolean folderClipChildren = mFolder.getClipChildren();
-        boolean folderClipToPadding = mFolder.getClipToPadding();
-        boolean contentClipChildren = mContent.getClipChildren();
-        boolean contentClipToPadding = mContent.getClipToPadding();
-        boolean cellLayoutClipChildren = cellLayout.getClipChildren();
-        boolean cellLayoutClipPadding = cellLayout.getClipToPadding();
-
-        mFolder.setClipChildren(false);
-        mFolder.setClipToPadding(false);
-        mContent.setClipChildren(false);
-        mContent.setClipToPadding(false);
-        cellLayout.setClipChildren(false);
-        cellLayout.setClipToPadding(false);
-
+        // Store clip variables.
+        // Because {@link #onAnimationStart} and {@link #onAnimationEnd} callbacks are sent to
+        // message queue and executed on separate frame, we should save states in
+        // {@link #onAnimationStart} instead of before creating animator, so that cancelling
+        // animation A and restarting animation B allows A to reset states in
+        // {@link #onAnimationEnd} before B reads new UI state from {@link #onAnimationStart}.
         a.addListener(new AnimatorListenerAdapter() {
+            private CellLayout mCellLayout;
+            private boolean mFolderClipToPadding;
+            private boolean mContentClipChildren;
+            private boolean mContentClipToPadding;
+            private boolean mCellLayoutClipChildren;
+            private boolean mCellLayoutClipPadding;
+
+            @Override
+            public void onAnimationStart(Animator animator) {
+                super.onAnimationStart(animator);
+                mCellLayout = mContent.getCurrentCellLayout();
+                mFolderClipToPadding = mFolder.getClipToPadding();
+                mContentClipChildren = mContent.getClipChildren();
+                mContentClipToPadding = mContent.getClipToPadding();
+                mCellLayoutClipChildren = mCellLayout.getClipChildren();
+                mCellLayoutClipPadding = mCellLayout.getClipToPadding();
+
+                mFolder.setClipToPadding(false);
+                mContent.setClipChildren(false);
+                mContent.setClipToPadding(false);
+                mCellLayout.setClipChildren(false);
+                mCellLayout.setClipToPadding(false);
+            }
+
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
@@ -295,13 +309,11 @@
                 mFolder.mFooter.setTranslationX(0f);
                 mFolder.mFolderName.setAlpha(1f);
 
-                mFolder.setClipChildren(folderClipChildren);
-                mFolder.setClipToPadding(folderClipToPadding);
-                mContent.setClipChildren(contentClipChildren);
-                mContent.setClipToPadding(contentClipToPadding);
-                cellLayout.setClipChildren(cellLayoutClipChildren);
-                cellLayout.setClipToPadding(cellLayoutClipPadding);
-
+                mFolder.setClipToPadding(mFolderClipToPadding);
+                mContent.setClipChildren(mContentClipChildren);
+                mContent.setClipToPadding(mContentClipToPadding);
+                mCellLayout.setClipChildren(mCellLayoutClipChildren);
+                mCellLayout.setClipToPadding(mCellLayoutClipPadding);
             }
         });
 
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index b438e86..47677ea 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -105,7 +105,6 @@
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 import com.android.launcher3.widget.LocalColorExtractor;
-import com.android.launcher3.widget.NavigableAppWidgetHostView;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 import com.android.launcher3.widget.util.WidgetSizes;
 
@@ -281,9 +280,7 @@
         } else {
             mWallpaperColorResources = null;
         }
-        mAppWidgetHost = FeatureFlags.WIDGETS_IN_LAUNCHER_PREVIEW.get()
-                ? new LauncherPreviewAppWidgetHost(context)
-                : null;
+        mAppWidgetHost = new LauncherPreviewAppWidgetHost(context);
     }
 
     /** Populate preview and render it. */
@@ -416,19 +413,8 @@
 
     private void inflateAndAddWidgets(
             LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo providerInfo) {
-        AppWidgetHostView view;
-        if (FeatureFlags.WIDGETS_IN_LAUNCHER_PREVIEW.get()) {
-            view = mAppWidgetHost.createView(mContext, info.appWidgetId, providerInfo);
-        } else {
-            view = new NavigableAppWidgetHostView(this) {
-                @Override
-                protected boolean shouldAllowDirectClick() {
-                    return false;
-                }
-            };
-            view.setAppWidget(-1, providerInfo);
-            view.updateAppWidget(null);
-        }
+        AppWidgetHostView view = mAppWidgetHost.createView(
+                mContext, info.appWidgetId, providerInfo);
 
         if (mWallpaperColorResources != null) {
             view.setColorResources(mWallpaperColorResources);
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 8f0b8ec..e89c0c5 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -51,7 +51,9 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
 import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.GridSizeMigrationUtil;
+import com.android.launcher3.model.LauncherBinder;
 import com.android.launcher3.model.LoaderTask;
 import com.android.launcher3.model.ModelDbController;
 import com.android.launcher3.provider.LauncherDbUtils;
@@ -226,12 +228,14 @@
             LauncherAppState.getInstance(previewContext)
                     .getModel().getModelDbController().clearEmptyDbFlag();
 
+            BgDataModel bgModel = new BgDataModel();
             new LoaderTask(
                     LauncherAppState.getInstance(previewContext),
                     /* bgAllAppsList= */ null,
-                    new BgDataModel(),
+                    bgModel,
                     LauncherAppState.getInstance(previewContext).getModel().getModelDelegate(),
-                    /* results= */ null) {
+                    new LauncherBinder(LauncherAppState.getInstance(previewContext), bgModel,
+                            /* bgAllAppsList= */ null, new Callbacks[0])) {
 
                 @Override
                 public void run() {
diff --git a/src/com/android/launcher3/keyboard/KeyboardDragAndDropView.java b/src/com/android/launcher3/keyboard/KeyboardDragAndDropView.java
index a6c897f..408a5a0 100644
--- a/src/com/android/launcher3/keyboard/KeyboardDragAndDropView.java
+++ b/src/com/android/launcher3/keyboard/KeyboardDragAndDropView.java
@@ -17,6 +17,7 @@
 
 import static android.app.Activity.DEFAULT_KEYS_SEARCH_LOCAL;
 
+import static com.android.launcher3.LauncherState.EDIT_MODE;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 
 import android.app.Activity;
@@ -114,7 +115,7 @@
 
     @Override
     public void onStateTransitionStart(LauncherState toState) {
-        if (toState != SPRING_LOADED) {
+        if (toState != SPRING_LOADED && toState != EDIT_MODE) {
             close(false);
         }
     }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index d4eded5..1a8cf24 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -51,6 +51,7 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
@@ -118,6 +119,7 @@
 
     private static final boolean DEBUG = true;
 
+    @NonNull
     protected final LauncherAppState mApp;
     private final AllAppsList mBgAllAppsList;
     protected final BgDataModel mBgDataModel;
@@ -125,6 +127,7 @@
 
     private FirstScreenBroadcast mFirstScreenBroadcast;
 
+    @NonNull
     private final LauncherBinder mLauncherBinder;
 
     private final LauncherApps mLauncherApps;
@@ -145,11 +148,11 @@
     private boolean mItemsDeleted = false;
     private String mDbName;
 
-    public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel,
-            ModelDelegate modelDelegate, LauncherBinder launcherBinder) {
+    public LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel,
+            ModelDelegate modelDelegate, @NonNull LauncherBinder launcherBinder) {
         mApp = app;
         mBgAllAppsList = bgAllAppsList;
-        mBgDataModel = dataModel;
+        mBgDataModel = bgModel;
         mModelDelegate = modelDelegate;
         mLauncherBinder = launcherBinder;
 
diff --git a/src/com/android/launcher3/states/EditModeState.kt b/src/com/android/launcher3/states/EditModeState.kt
new file mode 100644
index 0000000..aafaaa0
--- /dev/null
+++ b/src/com/android/launcher3/states/EditModeState.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.states
+
+import android.content.Context
+import com.android.launcher3.Launcher
+import com.android.launcher3.LauncherState
+import com.android.launcher3.logging.StatsLogManager
+import com.android.launcher3.views.ActivityContext
+
+/** Definition for Edit Mode state used for home gardening multi-select */
+class EditModeState(id: Int) : LauncherState(id, StatsLogManager.LAUNCHER_STATE_HOME, STATE_FLAGS) {
+
+    companion object {
+        private val STATE_FLAGS =
+            (FLAG_MULTI_PAGE or
+                FLAG_WORKSPACE_INACCESSIBLE or
+                FLAG_DISABLE_RESTORE or
+                FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED or
+                FLAG_WORKSPACE_HAS_BACKGROUNDS)
+    }
+
+    override fun <T> getTransitionDuration(context: T, isToState: Boolean): Int where
+    T : Context?,
+    T : ActivityContext? {
+        return 150
+    }
+
+    override fun <T> getDepthUnchecked(context: T): Float where T : Context?, T : ActivityContext? {
+        return 0.5f
+    }
+
+    override fun getWorkspaceScaleAndTranslation(launcher: Launcher): ScaleAndTranslation {
+        val scale = launcher.deviceProfile.getWorkspaceSpringLoadScale(launcher)
+        return ScaleAndTranslation(scale, 0f, 0f)
+    }
+
+    override fun getHotseatScaleAndTranslation(launcher: Launcher): ScaleAndTranslation {
+        val scale = launcher.deviceProfile.getWorkspaceSpringLoadScale(launcher)
+        return ScaleAndTranslation(scale, 0f, 0f)
+    }
+
+    override fun getWorkspaceBackgroundAlpha(launcher: Launcher): Float {
+        return 0.2f
+    }
+
+    override fun onLeavingState(launcher: Launcher?, toState: LauncherState?) {
+        // cleanup any changes to workspace
+    }
+}
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 7db7b0d..9cba19d 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -19,6 +19,7 @@
 import static android.view.View.VISIBLE;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.EDIT_MODE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_ITEM_LONG_PRESSED;
@@ -58,7 +59,11 @@
         }
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!canStartDrag(launcher)) return false;
-        if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
+        if (!launcher.isInState(NORMAL)
+                && !launcher.isInState(OVERVIEW)
+                && !launcher.isInState(EDIT_MODE)) {
+            return false;
+        }
         if (!(v.getTag() instanceof ItemInfo)) return false;
 
         launcher.setWaitingForResult(null);
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index badcd35..6a4e528 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -136,16 +136,19 @@
                 if (mDestroyed) {
                     Log.e(TAG, "Static object access with a destroyed context");
                 }
-                if (!mAllowedObjects.contains(object)) {
-                    throw new IllegalStateException(
-                            "Leaking unknown objects " + object + "  " + provider);
-                }
+
                 T t = (T) mObjectMap.get(object);
                 if (t != null) {
                     return t;
                 }
                 if (Looper.myLooper() == Looper.getMainLooper()) {
                     t = createObject(provider);
+                    // Check if we've explicitly allowed the object or if it's a SafeCloseable,
+                    // it will get destroyed in onDestroy()
+                    if (!mAllowedObjects.contains(object) && !(t instanceof SafeCloseable)) {
+                        throw new IllegalStateException(
+                                "Leaking unknown objects " + object + "  " + provider + " " + t);
+                    }
                     mObjectMap.put(object, t);
                     mOrderedObjects.add(t);
                     return t;
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 31b1934..515a2d8 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.views;
 
+import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR;
+
+import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
 import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_KEYBOARD_CLOSED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
@@ -45,7 +48,6 @@
 import android.view.WindowInsetsController;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Toast;
-import android.window.SplashScreen;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -289,27 +291,27 @@
         }
     }
 
-
     /**
      * Sends a pending intent animating from a view.
      *
      * @param v View to animate.
      * @param intent The pending intent being launched.
      * @param item Item associated with the view.
-     * @return {@code true} if the intent is sent successfully.
+     * @return RunnableList for listening for animation finish if the activity was properly
+     *         or started, {@code null} if the launch finished
      */
-    default boolean sendPendingIntentWithAnimation(
+    default RunnableList sendPendingIntentWithAnimation(
             @NonNull View v, PendingIntent intent, @Nullable ItemInfo item) {
-        Bundle optsBundle = getActivityLaunchOptions(v, item).toBundle();
+        ActivityOptionsWrapper options = getActivityLaunchOptions(v, item);
         try {
-            intent.send(null, 0, null, null, null, null, optsBundle);
-            return true;
+            intent.send(null, 0, null, null, null, null, options.toBundle());
+            return options.onEndCallback;
         } catch (PendingIntent.CanceledException e) {
             Toast.makeText(v.getContext(),
                     v.getContext().getResources().getText(R.string.shortcut_not_available),
                     Toast.LENGTH_SHORT).show();
         }
-        return false;
+        return null;
     }
 
     /**
@@ -318,28 +320,23 @@
      * @param v View starting the activity.
      * @param intent Base intent being launched.
      * @param item Item associated with the view.
-     * @return {@code true} if the activity starts successfully.
+     * @return RunnableList for listening for animation finish if the activity was properly
+     *         or started, {@code null} if the launch finished
      */
-    default boolean startActivitySafely(
+    default RunnableList startActivitySafely(
             View v, Intent intent, @Nullable ItemInfo item) {
         Preconditions.assertUIThread();
         Context context = (Context) this;
         if (isAppBlockedForSafeMode() && !PackageManagerHelper.isSystemApp(context, intent)) {
             Toast.makeText(context, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
-            return false;
+            return null;
         }
 
-        Bundle optsBundle = null;
-        if (v != null) {
-            optsBundle = getActivityLaunchOptions(v, item).toBundle();
-        } else if (android.os.Build.VERSION.SDK_INT >= 33
-                && item != null
-                && item.animationType == LauncherSettings.Animation.DEFAULT_NO_ICON) {
-            optsBundle = ActivityOptions.makeBasic()
-                    .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR).toBundle();
-        }
+        ActivityOptionsWrapper options = v != null ? getActivityLaunchOptions(v, item)
+                : makeDefaultActivityOptions(item != null && item.animationType == DEFAULT_NO_ICON
+                        ? SPLASH_SCREEN_STYLE_SOLID_COLOR : -1 /* SPLASH_SCREEN_STYLE_UNDEFINED */);
         UserHandle user = item == null ? null : item.user;
-
+        Bundle optsBundle = options.toBundle();
         // Prepare intent
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         if (v != null) {
@@ -364,12 +361,12 @@
                 InstanceId instanceId = new InstanceIdSequence().newInstanceId();
                 logAppLaunch(getStatsLogManager(), item, instanceId);
             }
-            return true;
+            return options.onEndCallback;
         } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
             Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
             Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
         }
-        return false;
+        return null;
     }
 
     /** Returns {@code true} if an app launch is blocked due to safe mode. */
@@ -417,6 +414,17 @@
     }
 
     /**
+     * Creates a default activity option and we do not want association with any launcher element.
+     */
+    default ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
+        ActivityOptions options = ActivityOptions.makeBasic();
+        if (Utilities.ATLEAST_T) {
+            options.setSplashScreenStyle(splashScreenStyle);
+        }
+        return new ActivityOptionsWrapper(options, new RunnableList());
+    }
+
+    /**
      * Safely launches an intent for a shortcut.
      *
      * @param intent Intent to start.
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 64ad390..4641e31 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -17,6 +17,7 @@
 
 import static androidx.core.content.ContextCompat.getColorStateList;
 
+import static com.android.launcher3.LauncherState.EDIT_MODE;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_MATERIAL_U_POPUP;
 import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
@@ -222,6 +223,8 @@
     }
 
     private static boolean enterHomeGardening(View view) {
+        Launcher launcher = Launcher.getLauncher(view.getContext());
+        launcher.getStateManager().goToState(EDIT_MODE);
         return true;
     }
 
@@ -281,7 +284,7 @@
         if (!TextUtils.isEmpty(pickerPackage)) {
             intent.setPackage(pickerPackage);
         }
-        return launcher.startActivitySafely(v, intent, placeholderInfo(intent));
+        return launcher.startActivitySafely(v, intent, placeholderInfo(intent)) != null;
     }
 
     static WorkspaceItemInfo placeholderInfo(Intent intent) {
diff --git a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt b/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
index 971fd9b..ac0a166 100644
--- a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
+++ b/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
@@ -25,6 +25,7 @@
 import com.android.launcher3.R
 import com.android.launcher3.util.ResourceHelper
 import java.io.IOException
+import kotlin.math.roundToInt
 import org.xmlpull.v1.XmlPullParser
 import org.xmlpull.v1.XmlPullParserException
 
@@ -159,6 +160,77 @@
             }
         }
     }
+
+    /**
+     * Returns the CalculatedWorkspaceSpec for width, based on the available width and the
+     * WorkspaceSpecs.
+     */
+    fun getCalculatedWidthSpec(columns: Int, availableWidth: Int): CalculatedWorkspaceSpec {
+        val widthSpec = workspaceWidthSpecList.first { availableWidth <= it.maxAvailableSize }
+
+        return CalculatedWorkspaceSpec(availableWidth, columns, widthSpec)
+    }
+
+    /**
+     * Returns the CalculatedWorkspaceSpec for height, based on the available height and the
+     * WorkspaceSpecs.
+     */
+    fun getCalculatedHeightSpec(rows: Int, availableHeight: Int): CalculatedWorkspaceSpec {
+        val heightSpec = workspaceHeightSpecList.first { availableHeight <= it.maxAvailableSize }
+
+        return CalculatedWorkspaceSpec(availableHeight, rows, heightSpec)
+    }
+}
+
+class CalculatedWorkspaceSpec(
+    val availableSpace: Int,
+    val cells: Int,
+    val workspaceSpec: WorkspaceSpec
+) {
+    var startPaddingPx: Int = 0
+        private set
+    var endPaddingPx: Int = 0
+        private set
+    var gutterPx: Int = 0
+        private set
+    var cellSizePx: Int = 0
+        private set
+    init {
+        // Calculate all fixed size first
+        if (workspaceSpec.startPadding.fixedSize > 0)
+            startPaddingPx = workspaceSpec.startPadding.fixedSize.roundToInt()
+        if (workspaceSpec.endPadding.fixedSize > 0)
+            endPaddingPx = workspaceSpec.endPadding.fixedSize.roundToInt()
+        if (workspaceSpec.gutter.fixedSize > 0)
+            gutterPx = workspaceSpec.gutter.fixedSize.roundToInt()
+        if (workspaceSpec.cellSize.fixedSize > 0)
+            cellSizePx = workspaceSpec.cellSize.fixedSize.roundToInt()
+
+        // Calculate all available space next
+        if (workspaceSpec.startPadding.ofAvailableSpace > 0)
+            startPaddingPx =
+                (workspaceSpec.startPadding.ofAvailableSpace * availableSpace).roundToInt()
+        if (workspaceSpec.endPadding.ofAvailableSpace > 0)
+            endPaddingPx = (workspaceSpec.endPadding.ofAvailableSpace * availableSpace).roundToInt()
+        if (workspaceSpec.gutter.ofAvailableSpace > 0)
+            gutterPx = (workspaceSpec.gutter.ofAvailableSpace * availableSpace).roundToInt()
+        if (workspaceSpec.cellSize.ofAvailableSpace > 0)
+            cellSizePx = (workspaceSpec.cellSize.ofAvailableSpace * availableSpace).roundToInt()
+
+        // Calculate remainder space last
+        val gutters = cells - 1
+        val usedSpace = startPaddingPx + endPaddingPx + (gutterPx * gutters) + (cellSizePx * cells)
+        val remainderSpace = availableSpace - usedSpace
+        if (workspaceSpec.startPadding.ofRemainderSpace > 0)
+            startPaddingPx =
+                (workspaceSpec.startPadding.ofRemainderSpace * remainderSpace).roundToInt()
+        if (workspaceSpec.endPadding.ofRemainderSpace > 0)
+            endPaddingPx = (workspaceSpec.endPadding.ofRemainderSpace * remainderSpace).roundToInt()
+        if (workspaceSpec.gutter.ofRemainderSpace > 0)
+            gutterPx = (workspaceSpec.gutter.ofRemainderSpace * remainderSpace).roundToInt()
+        if (workspaceSpec.cellSize.ofRemainderSpace > 0)
+            cellSizePx = (workspaceSpec.cellSize.ofRemainderSpace * remainderSpace).roundToInt()
+    }
 }
 
 data class WorkspaceSpec(
@@ -220,7 +292,7 @@
 
     fun isValid(): Boolean {
         // All attributes are empty
-        if (fixedSize <= 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f) {
+        if (fixedSize < 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f) {
             Log.e(TAG, "SizeSpec#isValid - all attributes are empty")
             return false
         }
diff --git a/tests/Android.bp b/tests/Android.bp
index fa0cdf2..e7f4084 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -51,6 +51,7 @@
       "src/com/android/launcher3/util/WidgetUtils.java",
       "src/com/android/launcher3/util/rule/FailureWatcher.java",
       "src/com/android/launcher3/util/rule/LauncherActivityRule.java",
+      "src/com/android/launcher3/util/rule/ViewCaptureRule.kt",
       "src/com/android/launcher3/util/rule/SamplerRule.java",
       "src/com/android/launcher3/util/rule/ScreenRecordRule.java",
       "src/com/android/launcher3/util/rule/ShellCommandRule.java",
@@ -132,4 +133,4 @@
     manifest: "shared/AndroidManifest.xml",
     sdk_version: "current",
     min_sdk_version: min_launcher3_sdk_version,
- }
\ No newline at end of file
+ }
diff --git a/tests/res/xml/valid_workspace_file.xml b/tests/res/xml/valid_workspace_file.xml
index 91a3e48..1f97314 100644
--- a/tests/res/xml/valid_workspace_file.xml
+++ b/tests/res/xml/valid_workspace_file.xml
@@ -15,43 +15,45 @@
   -->
 
 <workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <!-- 584 grid height -->
     <workspaceSpec
         launcher:specType="height"
-        launcher:maxAvailableSize="648dp">
-        <startPadding
-            launcher:ofAvailableSpace="0.0125" />
-        <endPadding
-            launcher:ofAvailableSpace="0.05" />
-        <gutter
-            launcher:fixedSize="16dp" />
-        <cellSize
-            launcher:ofRemainderSpace="0.2" />
+        launcher:maxAvailableSize="584dp">
+        <startPadding launcher:fixedSize="0dp" />
+        <endPadding launcher:fixedSize="32dp" />
+        <gutter launcher:fixedSize="16dp" />
+        <cellSize launcher:ofAvailableSpace="0.15808" />
+    </workspaceSpec>
+
+    <!-- 584 grid height + 28 remainder space -->
+    <workspaceSpec
+        launcher:specType="height"
+        launcher:maxAvailableSize="612dp">
+        <startPadding launcher:fixedSize="0dp" />
+        <endPadding launcher:ofRemainderSpace="1" />
+        <gutter launcher:fixedSize="16dp" />
+        <cellSize launcher:fixedSize="104dp" />
     </workspaceSpec>
 
     <workspaceSpec
         launcher:specType="height"
         launcher:maxAvailableSize="9999dp">
-        <startPadding
-            launcher:ofAvailableSpace="0.0306" />
-        <endPadding
-            launcher:ofAvailableSpace="0.068" />
-        <gutter
-            launcher:fixedSize="16dp" />
-        <cellSize
-            launcher:ofRemainderSpace="0.2" />
+        <startPadding launcher:fixedSize="8dp" />
+        <endPadding launcher:ofRemainderSpace="1" />
+        <gutter launcher:fixedSize="16dp" />
+        <cellSize launcher:fixedSize="104dp" />
     </workspaceSpec>
 
+    <!--  TODO(b/241386436): other specs here for height ...  -->
+
     <!-- Width spec is always the same -->
     <workspaceSpec
         launcher:specType="width"
         launcher:maxAvailableSize="9999dp">
-        <startPadding
-            launcher:ofRemainderSpace="0.21436227" />
-        <endPadding
-            launcher:ofRemainderSpace="0.21436227" />
-        <gutter
-            launcher:ofRemainderSpace="0.11425509" />
-        <cellSize
-            launcher:fixedSize="120dp" />
+        <startPadding launcher:fixedSize="22dp" />
+        <endPadding launcher:fixedSize="22dp" />
+        <gutter launcher:fixedSize="16dp" />
+        <cellSize launcher:ofRemainderSpace="0.25" />
     </workspaceSpec>
+
 </workspaceSpecs>
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 601b07e..2b67cdd 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -36,6 +36,7 @@
     public static final int HINT_STATE_ORDINAL = 7;
     public static final int HINT_STATE_TWO_BUTTON_ORDINAL = 8;
     public static final int OVERVIEW_SPLIT_SELECT_ORDINAL = 9;
+    public static final int EDIT_MODE_STATE_ORDINAL = 10;
     public static final String TAPL_EVENTS_TAG = "TaplEvents";
     public static final String SEQUENCE_MAIN = "Main";
     public static final String SEQUENCE_TIS = "TIS";
@@ -63,6 +64,8 @@
                 return "Hint2Button";
             case OVERVIEW_SPLIT_SELECT_ORDINAL:
                 return "OverviewSplitSelect";
+            case EDIT_MODE_STATE_ORDINAL:
+                return "EditMode";
             default:
                 return "Unknown";
         }
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 01f494b..3de4d55 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -66,7 +66,7 @@
 
     class DeviceSpec(
         val naturalSize: Pair<Int, Int>,
-        val densityDpi: Int,
+        var densityDpi: Int,
         val statusBarNaturalPx: Int,
         val statusBarRotatedPx: Int,
         val gesturePx: Int,
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 3141c7b..d7c4ae3 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -72,6 +72,7 @@
 import com.android.launcher3.util.rule.ScreenRecordRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.util.rule.TestStabilityRule;
+import com.android.launcher3.util.rule.ViewCaptureRule;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -215,14 +216,15 @@
     }
 
     protected TestRule getRulesInsideActivityMonitor() {
+        final ViewCaptureRule viewCaptureRule = new ViewCaptureRule();
         final RuleChain inner = RuleChain
                 .outerRule(new PortraitLandscapeRunner(this))
-                .around(new FailureWatcher(mDevice, mLauncher));
+                .around(viewCaptureRule)
+                .around(new FailureWatcher(mDevice, mLauncher, viewCaptureRule.getViewCapture()));
 
         return TestHelpers.isInLauncherProcess()
-                ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher())
-                .around(inner) :
-                inner;
+                ? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner)
+                : inner;
     }
 
     @Rule
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 51facf4..19e7b13 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -7,8 +7,12 @@
 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.uiautomator.UiDevice;
 
+import com.android.app.viewcapture.ViewCapture;
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 
@@ -29,10 +33,14 @@
     private static boolean sSavedBugreport = false;
     final private UiDevice mDevice;
     private final LauncherInstrumentation mLauncher;
+    @NonNull
+    private final ViewCapture mViewCapture;
 
-    public FailureWatcher(UiDevice device, LauncherInstrumentation launcher) {
+    public FailureWatcher(UiDevice device, LauncherInstrumentation launcher,
+            @NonNull ViewCapture viewCapture) {
         mDevice = device;
         mLauncher = launcher;
+        mViewCapture = viewCapture;
     }
 
     @Override
@@ -82,7 +90,7 @@
 
     @Override
     protected void failed(Throwable e, Description description) {
-        onError(mLauncher, description, e);
+        onError(mLauncher, description, e, mViewCapture);
     }
 
     static File diagFile(Description description, String prefix, String ext) {
@@ -93,8 +101,12 @@
 
     public static void onError(LauncherInstrumentation launcher, Description description,
             Throwable e) {
-        final UiDevice device = launcher.getDevice();
-        if (device == null) return;
+        onError(launcher, description, e, null);
+    }
+
+    private static void onError(LauncherInstrumentation launcher, Description description,
+            Throwable e, @Nullable ViewCapture viewCapture) {
+
         final File sceenshot = diagFile(description, "TestScreenshot", "png");
         final File hierarchy = diagFile(description, "Hierarchy", "zip");
 
@@ -109,13 +121,20 @@
             out.putNextEntry(new ZipEntry("visible_windows.zip"));
             dumpCommand("cmd window dump-visible-window-views", out);
             out.closeEntry();
-        } catch (IOException ex) {
+
+            if (viewCapture != null) {
+                out.putNextEntry(new ZipEntry("FS/data/misc/wmtrace/failed_test.vc"));
+                viewCapture.dumpTo(out, ApplicationProvider.getApplicationContext());
+                out.closeEntry();
+            }
+        } catch (Exception ignored) {
         }
 
         Log.e(TAG, "Failed test " + description.getMethodName()
                 + ",\nscreenshot will be saved to " + sceenshot
                 + ",\nUI dump at: " + hierarchy
                 + " (use go/web-hv to open the dump file)", e);
+        final UiDevice device = launcher.getDevice();
         device.takeScreenshot(sceenshot);
 
         // Dump accessibility hierarchy
diff --git a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
index 2093682..e9a52f8 100644
--- a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
@@ -56,4 +56,4 @@
             return launcher.getWorkspace().getFirstMatch(op) != null;
         };
     }
-}
+}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
index 1dbba6a..2eedec3 100644
--- a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
@@ -102,4 +102,4 @@
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
new file mode 100644
index 0000000..e4713b2
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/ViewCaptureRule.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.launcher3.util.rule
+
+import android.app.Activity
+import android.app.Application
+import android.media.permission.SafeCloseable
+import android.os.Bundle
+import androidx.test.core.app.ApplicationProvider
+import com.android.app.viewcapture.SimpleViewCapture
+import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * This JUnit TestRule registers a listener for activity lifecycle events to attach a ViewCapture
+ * instance that other test rules use to dump the timelapse hierarchy upon an error during a test.
+ *
+ * This rule will not work in OOP tests that don't have access to the activity under test.
+ */
+class ViewCaptureRule : TestRule {
+    val viewCapture = SimpleViewCapture("test-view-capture")
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return object : Statement() {
+            override fun evaluate() {
+                val windowListenerCloseables = mutableListOf<SafeCloseable>()
+
+                val lifecycleCallbacks =
+                    object : ActivityLifecycleCallbacksAdapter {
+                        override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
+                            super.onActivityCreated(activity, bundle)
+                            windowListenerCloseables.add(
+                                viewCapture.startCapture(
+                                    activity.window.decorView,
+                                    "${description.testClass?.simpleName}.${description.methodName}"
+                                )
+                            )
+                        }
+
+                        override fun onActivityDestroyed(activity: Activity) {
+                            super.onActivityDestroyed(activity)
+                            viewCapture.stopCapture(activity.window.decorView)
+                        }
+                    }
+
+                val application = ApplicationProvider.getApplicationContext<Application>()
+                application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
+
+                try {
+                    base.evaluate()
+                } finally {
+                    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
+
+                    // Clean up ViewCapture references here rather than in onActivityDestroyed so
+                    // test code can access view hierarchy capture. onActivityDestroyed would delete
+                    // view capture data before FailureWatcher could output it as a test artifact.
+                    windowListenerCloseables.onEach(SafeCloseable::close)
+                }
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt b/tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt
new file mode 100644
index 0000000..7f03ba2
--- /dev/null
+++ b/tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.workspace
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.tests.R as TestR
+import com.android.launcher3.util.TestResourceHelper
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CalculatedWorkspaceSpecTest : AbstractDeviceProfileTest() {
+    override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+    /**
+     * This test tests:
+     * - (height spec) gets the correct breakpoint from the XML - skips the first 2 breakpoints
+     * - (height spec) do the correct calculations for available space and fixed size
+     * - (width spec) do the correct calculations for remainder space and fixed size
+     */
+    @Test
+    fun normalPhone_returnsThirdBreakpointSpec() {
+        val deviceSpec = deviceSpecs["phone"]!!
+        initializeVarsForPhone(deviceSpec)
+
+        val availableWidth = deviceSpec.naturalSize.first
+        // Hotseat size is roughly 495px on a real device,
+        // it doesn't need to be precise on unit tests
+        val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 495
+
+        val workspaceSpecs =
+            WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
+        val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
+        val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
+
+        assertThat(widthSpec.availableSpace).isEqualTo(availableWidth)
+        assertThat(widthSpec.cells).isEqualTo(4)
+        assertThat(widthSpec.startPaddingPx).isEqualTo(58)
+        assertThat(widthSpec.endPaddingPx).isEqualTo(58)
+        assertThat(widthSpec.gutterPx).isEqualTo(42)
+        assertThat(widthSpec.cellSizePx).isEqualTo(210)
+
+        assertThat(heightSpec.availableSpace).isEqualTo(availableHeight)
+        assertThat(heightSpec.cells).isEqualTo(5)
+        assertThat(heightSpec.startPaddingPx).isEqualTo(21)
+        assertThat(heightSpec.endPaddingPx).isEqualTo(233)
+        assertThat(heightSpec.gutterPx).isEqualTo(42)
+        assertThat(heightSpec.cellSizePx).isEqualTo(273)
+    }
+
+    /**
+     * This test tests:
+     * - (height spec) gets the correct breakpoint from the XML - use the first breakpoint
+     * - (height spec) do the correct calculations for remainder space and fixed size
+     * - (width spec) do the correct calculations for remainder space and fixed size
+     */
+    @Test
+    fun smallPhone_returnsFirstBreakpointSpec() {
+        val deviceSpec = deviceSpecs["phone"]!!
+        deviceSpec.densityDpi = 540 // larger display size
+        initializeVarsForPhone(deviceSpec)
+
+        val availableWidth = deviceSpec.naturalSize.first
+        // Hotseat size is roughly 640px on a real device,
+        // it doesn't need to be precise on unit tests
+        val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 640
+
+        val workspaceSpecs =
+            WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
+        val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
+        val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
+
+        assertThat(widthSpec.availableSpace).isEqualTo(availableWidth)
+        assertThat(widthSpec.cells).isEqualTo(4)
+        assertThat(widthSpec.startPaddingPx).isEqualTo(74)
+        assertThat(widthSpec.endPaddingPx).isEqualTo(74)
+        assertThat(widthSpec.gutterPx).isEqualTo(54)
+        assertThat(widthSpec.cellSizePx).isEqualTo(193)
+
+        assertThat(heightSpec.availableSpace).isEqualTo(availableHeight)
+        assertThat(heightSpec.cells).isEqualTo(5)
+        assertThat(heightSpec.startPaddingPx).isEqualTo(0)
+        assertThat(heightSpec.endPaddingPx).isEqualTo(108)
+        assertThat(heightSpec.gutterPx).isEqualTo(54)
+        assertThat(heightSpec.cellSizePx).isEqualTo(260)
+    }
+}
diff --git a/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt b/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
index 0fd8a54..9cd0a2e 100644
--- a/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
+++ b/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
@@ -42,43 +42,62 @@
     fun parseValidFile() {
         val workspaceSpecs =
             WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
-        assertThat(workspaceSpecs.workspaceHeightSpecList.size).isEqualTo(2)
+        assertThat(workspaceSpecs.workspaceHeightSpecList.size).isEqualTo(3)
         assertThat(workspaceSpecs.workspaceHeightSpecList[0].toString())
             .isEqualTo(
                 "WorkspaceSpec(" +
-                    "maxAvailableSize=1701, " +
+                    "maxAvailableSize=1533, " +
                     "specType=HEIGHT, " +
                     "startPadding=SizeSpec(fixedSize=0.0, " +
-                    "ofAvailableSpace=0.0125, " +
+                    "ofAvailableSpace=0.0, " +
                     "ofRemainderSpace=0.0), " +
-                    "endPadding=SizeSpec(fixedSize=0.0, " +
-                    "ofAvailableSpace=0.05, " +
+                    "endPadding=SizeSpec(fixedSize=84.0, " +
+                    "ofAvailableSpace=0.0, " +
                     "ofRemainderSpace=0.0), " +
                     "gutter=SizeSpec(fixedSize=42.0, " +
                     "ofAvailableSpace=0.0, " +
                     "ofRemainderSpace=0.0), " +
                     "cellSize=SizeSpec(fixedSize=0.0, " +
-                    "ofAvailableSpace=0.0, " +
-                    "ofRemainderSpace=0.2)" +
+                    "ofAvailableSpace=0.15808, " +
+                    "ofRemainderSpace=0.0)" +
                     ")"
             )
         assertThat(workspaceSpecs.workspaceHeightSpecList[1].toString())
             .isEqualTo(
                 "WorkspaceSpec(" +
-                    "maxAvailableSize=26247, " +
+                    "maxAvailableSize=1607, " +
                     "specType=HEIGHT, " +
                     "startPadding=SizeSpec(fixedSize=0.0, " +
-                    "ofAvailableSpace=0.0306, " +
+                    "ofAvailableSpace=0.0, " +
                     "ofRemainderSpace=0.0), " +
                     "endPadding=SizeSpec(fixedSize=0.0, " +
-                    "ofAvailableSpace=0.068, " +
-                    "ofRemainderSpace=0.0), " +
+                    "ofAvailableSpace=0.0, " +
+                    "ofRemainderSpace=1.0), " +
                     "gutter=SizeSpec(fixedSize=42.0, " +
                     "ofAvailableSpace=0.0, " +
                     "ofRemainderSpace=0.0), " +
-                    "cellSize=SizeSpec(fixedSize=0.0, " +
+                    "cellSize=SizeSpec(fixedSize=273.0, " +
                     "ofAvailableSpace=0.0, " +
-                    "ofRemainderSpace=0.2)" +
+                    "ofRemainderSpace=0.0)" +
+                    ")"
+            )
+        assertThat(workspaceSpecs.workspaceHeightSpecList[2].toString())
+            .isEqualTo(
+                "WorkspaceSpec(" +
+                    "maxAvailableSize=26247, " +
+                    "specType=HEIGHT, " +
+                    "startPadding=SizeSpec(fixedSize=21.0, " +
+                    "ofAvailableSpace=0.0, " +
+                    "ofRemainderSpace=0.0), " +
+                    "endPadding=SizeSpec(fixedSize=0.0, " +
+                    "ofAvailableSpace=0.0, " +
+                    "ofRemainderSpace=1.0), " +
+                    "gutter=SizeSpec(fixedSize=42.0, " +
+                    "ofAvailableSpace=0.0, " +
+                    "ofRemainderSpace=0.0), " +
+                    "cellSize=SizeSpec(fixedSize=273.0, " +
+                    "ofAvailableSpace=0.0, " +
+                    "ofRemainderSpace=0.0)" +
                     ")"
             )
         assertThat(workspaceSpecs.workspaceWidthSpecList.size).isEqualTo(1)
@@ -87,18 +106,18 @@
                 "WorkspaceSpec(" +
                     "maxAvailableSize=26247, " +
                     "specType=WIDTH, " +
-                    "startPadding=SizeSpec(fixedSize=0.0, " +
+                    "startPadding=SizeSpec(fixedSize=58.0, " +
                     "ofAvailableSpace=0.0, " +
-                    "ofRemainderSpace=0.21436226), " +
-                    "endPadding=SizeSpec(fixedSize=0.0, " +
+                    "ofRemainderSpace=0.0), " +
+                    "endPadding=SizeSpec(fixedSize=58.0, " +
                     "ofAvailableSpace=0.0, " +
-                    "ofRemainderSpace=0.21436226), " +
-                    "gutter=SizeSpec(fixedSize=0.0, " +
+                    "ofRemainderSpace=0.0), " +
+                    "gutter=SizeSpec(fixedSize=42.0, " +
                     "ofAvailableSpace=0.0, " +
-                    "ofRemainderSpace=0.11425509), " +
-                    "cellSize=SizeSpec(fixedSize=315.0, " +
+                    "ofRemainderSpace=0.0), " +
+                    "cellSize=SizeSpec(fixedSize=0.0, " +
                     "ofAvailableSpace=0.0, " +
-                    "ofRemainderSpace=0.0)" +
+                    "ofRemainderSpace=0.25)" +
                     ")"
             )
     }