Merge "Widget Picker - show app icon next to recommended app label." into main
diff --git a/Android.bp b/Android.bp
index 4354b66..8f2e5a6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -204,6 +204,7 @@
         "com_android_launcher3_flags_lib",
         "com_android_wm_shell_flags_lib",
         "android.appwidget.flags-aconfig-java",
+        "com.android.window.flags.window-aconfig-java",
     ],
     sdk_version: "current",
     min_sdk_version: min_launcher3_sdk_version,
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 33c973b..a37141c 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -186,3 +186,23 @@
     description: "Use an activity for home screen overlay"
     bug: "273828110"
 }
+
+flag {
+    name: "grid_migration_fix"
+    namespace: "launcher"
+    description: "Keep items in place when migrating to a bigger grid"
+    bug: "325286145"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "narrow_grid_restore"
+    namespace: "launcher"
+    description: "Using only the most recent workspace when restoring to avoid confusion."
+    bug: "325285743"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/go/quickstep/res/layout/overview_actions_container.xml b/go/quickstep/res/layout/overview_actions_container.xml
index 48650aa..077cfae 100644
--- a/go/quickstep/res/layout/overview_actions_container.xml
+++ b/go/quickstep/res/layout/overview_actions_container.xml
@@ -120,6 +120,16 @@
             android:layout_height="1dp"
             android:layout_weight="1"
             android:visibility="gone" />
+
+        <Button
+            android:id="@+id/action_save_app_pair"
+            style="@style/GoOverviewActionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:drawableStart="@drawable/ic_save_app_pair"
+            android:text="@string/action_save_app_pair"
+            android:theme="@style/ThemeControlHighlightWorkspaceColor"
+            android:visibility="gone" />
     </LinearLayout>
 
 </com.android.quickstep.views.GoOverviewActionsView>
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index 0fda0bf..5bd5823 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -55,6 +55,15 @@
             android:theme="@style/ThemeControlHighlightWorkspaceColor"
             android:visibility="gone" />
 
+        <Button
+            android:id="@+id/action_save_app_pair"
+            style="@style/OverviewActionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/action_save_app_pair"
+            android:theme="@style/ThemeControlHighlightWorkspaceColor"
+            android:visibility="gone" />
+
         <Space
             android:layout_width="0dp"
             android:layout_height="1dp"
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 14f615e..75a4fc8 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -230,6 +230,8 @@
     <string name="action_screenshot">Screenshot</string>
     <!-- Label for a button that enters split screen selection mode. [CHAR_LIMIT=20] -->
     <string name="action_split">Split</string>
+    <!-- Label for a button that saves a new app pair. [CHAR_LIMIT=20] -->
+    <string name="action_save_app_pair">Save app pair</string>
     <!-- Label for toast with instructions for split screen selection mode. [CHAR_LIMIT=50] -->
     <string name="toast_split_select_app">Tap another app to use split screen</string>
     <string name="toast_contextual_split_select_app">Choose another app to use split screen</string>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 7692760..14d46d1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -206,19 +206,12 @@
 
         if (mDeferUpdatesForSUW) {
             ItemInfo[] finalHotseatItemInfos = hotseatItemInfos;
-            mDeferredUpdates = () -> {
-                updateHotseatItemsAndBackground(finalHotseatItemInfos);
-            };
+            mDeferredUpdates = () -> mContainer.updateHotseatItems(finalHotseatItemInfos);
         } else {
-            updateHotseatItemsAndBackground(hotseatItemInfos);
+            mContainer.updateHotseatItems(hotseatItemInfos);
         }
     }
 
-    private void updateHotseatItemsAndBackground(ItemInfo[] hotseatItemInfos) {
-        mContainer.updateHotseatItems(hotseatItemInfos);
-        mControllers.taskbarViewController.updateIconsBackground();
-    }
-
     /**
      * This is used to defer UI updates after SUW builds the unstash animation.
      * @param defer if true, defers updates to the UI
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index e81d20a..854b0d0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -310,8 +310,16 @@
 
         boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
         boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible;
-        boolean isStashedInAppAuto =
-                isTransientTaskbar && !mTaskbarSharedState.getTaskbarWasPinned();
+        boolean taskbarWasPinned = mTaskbarSharedState.getTaskbarWasPinned();
+        boolean isStashedInAppAuto = isTransientTaskbar && !taskbarWasPinned;
+
+        // now that we know we need to keep transient taskbar unstashed after unpinning animation
+        // we need to reset the shared state, so everytime user recreates taskbar we don't unstash
+        // transient taskbar by default.
+        if (mTaskbarSharedState.getTaskbarWasPinned()) {
+            mTaskbarSharedState.setTaskbarWasPinned(false);
+        }
+
         if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
             isStashedInAppAuto = isStashedInAppAuto && mTaskbarSharedState.taskbarWasStashedAuto;
         }
@@ -324,8 +332,7 @@
         // us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
         updateStateForFlag(FLAG_IN_APP, true);
         applyState(/* duration = */ 0);
-        if (mTaskbarSharedState.getTaskbarWasPinned()
-                || !mTaskbarSharedState.taskbarWasStashedAuto) {
+        if (taskbarWasPinned || !mTaskbarSharedState.taskbarWasStashedAuto) {
             tryStartTaskbarTimeout();
         }
         notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index d9e2330..367bf6c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -52,7 +52,6 @@
 import com.android.launcher3.apppairs.AppPairIcon;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.PreviewBackground;
-import com.android.launcher3.icons.ThemedIconDrawable;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -60,7 +59,6 @@
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.DoubleShadowBubbleTextView;
 import com.android.launcher3.views.IconButtonView;
 
 import java.util.function.Predicate;
@@ -410,21 +408,6 @@
     }
 
     /**
-     * Traverse all the child views and change the background of themeIcons
-     **/
-    public void setThemedIconsBackgroundColor(int color) {
-        for (View icon : getIconViews()) {
-            if (icon instanceof DoubleShadowBubbleTextView) {
-                DoubleShadowBubbleTextView textView = ((DoubleShadowBubbleTextView) icon);
-                if (textView.getIcon() != null
-                        && textView.getIcon() instanceof ThemedIconDrawable) {
-                    ((ThemedIconDrawable) textView.getIcon()).changeBackgroundColor(color);
-                }
-            }
-        }
-    }
-
-    /**
      * Sets OnClickListener and OnLongClickListener for the given view.
      */
     public void setClickAndLongClickListenersForIcon(View icon) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 5494853..4b1963b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -45,7 +45,6 @@
 import android.view.animation.Interpolator;
 
 import androidx.annotation.Nullable;
-import androidx.core.graphics.ColorUtils;
 import androidx.core.view.OneShotPreDrawListener;
 
 import com.android.app.animation.Interpolators;
@@ -62,7 +61,6 @@
 import com.android.launcher3.anim.RevealOutlineAnimation;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.icons.ThemedIconDrawable;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -93,8 +91,6 @@
     public static final int ALPHA_INDEX_SMALL_SCREEN = 6;
     private static final int NUM_ALPHA_CHANNELS = 7;
 
-    private static final float TASKBAR_DARK_THEME_ICONS_BACKGROUND_LUMINANCE = 0.30f;
-
     private final TaskbarActivityContext mActivity;
     private final TaskbarView mTaskbarView;
     private final MultiValueAlpha mTaskbarIconAlpha;
@@ -125,12 +121,6 @@
 
     private int mTaskbarBottomMargin;
     private final int mStashedHandleHeight;
-    private final int mLauncherThemedIconsBackgroundColor;
-    private final int mTaskbarThemedIconsBackgroundColor;
-
-    /** Progress from {@code 0} for Launcher's color to {@code 1} for Taskbar's color. */
-    private final AnimatedFloat mThemedIconsBackgroundProgress = new AnimatedFloat(
-            this::updateIconsBackground);
 
     private final TaskbarModelCallbacks mModelCallbacks;
 
@@ -173,16 +163,7 @@
         mTaskbarBottomMargin = activity.getDeviceProfile().taskbarBottomMargin;
         mStashedHandleHeight = activity.getResources()
                 .getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height);
-        mLauncherThemedIconsBackgroundColor = ThemedIconDrawable.getColors(mActivity)[0];
-        if (!Utilities.isDarkTheme(mActivity)) {
-            mTaskbarThemedIconsBackgroundColor = mLauncherThemedIconsBackgroundColor;
-        } else {
-            // Increase luminance for dark themed icons given they are on a dark Taskbar background.
-            float[] colorHSL = new float[3];
-            ColorUtils.colorToHSL(mLauncherThemedIconsBackgroundColor, colorHSL);
-            colorHSL[2] = TASKBAR_DARK_THEME_ICONS_BACKGROUND_LUMINANCE;
-            mTaskbarThemedIconsBackgroundColor = ColorUtils.HSLToColor(colorHSL);
-        }
+
         mIsRtl = Utilities.isRtl(mTaskbarView.getResources());
         mTaskbarLeftRightMargin = mActivity.getResources().getDimensionPixelSize(
                 R.dimen.transient_taskbar_padding);
@@ -470,18 +451,6 @@
         return taskbarIconTranslationYForPinningValue;
     }
 
-    /**
-     * Updates the Taskbar's themed icons background according to the progress between in-app/home.
-     */
-    protected void updateIconsBackground() {
-        mTaskbarView.setThemedIconsBackgroundColor(
-                ColorUtils.blendARGB(
-                        mLauncherThemedIconsBackgroundColor,
-                        mTaskbarThemedIconsBackgroundColor,
-                        mThemedIconsBackgroundProgress.value
-                ));
-    }
-
     private ValueAnimator createRevealAnimForView(View view, boolean isStashed, float newWidth,
             boolean isQsb, boolean dispatchOnAnimationStart) {
         Rect viewBounds = new Rect(0, 0, view.getWidth(), view.getHeight());
@@ -679,10 +648,6 @@
         setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, interpolator);
         setter.setFloat(mTaskbarNavButtonTranslationYForInAppDisplay, VALUE, offsetY, interpolator);
 
-        if (Utilities.isDarkTheme(mTaskbarView.getContext())) {
-            setter.addFloat(mThemedIconsBackgroundProgress, VALUE, 1f, 0f, LINEAR);
-        }
-
         int collapsedHeight = mActivity.getDefaultTaskbarWindowSize();
         int expandedHeight = Math.max(collapsedHeight, taskbarDp.taskbarHeight + offsetY);
         setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowSize(
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index dbaeafb..2eced74 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -112,7 +112,8 @@
         boolean exitingOverview = !FeatureFlags.enableSplitContextually() && !toState.overviewUi;
         if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
             setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
-                    .createPlaceholderDismissAnim(mLauncher, LAUNCHER_SPLIT_SELECTION_EXIT_HOME));
+                    .createPlaceholderDismissAnim(mLauncher, LAUNCHER_SPLIT_SELECTION_EXIT_HOME,
+                            setter.getDuration()));
             setter.setViewAlpha(
                     mRecentsView.getSplitInstructionsView(),
                     0,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 7558b00..2c45129 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -200,7 +200,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.function.Consumer;
+import java.util.function.BiConsumer;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
@@ -599,9 +599,10 @@
 
         ArrayList<TouchController> list = new ArrayList<>();
         list.add(getDragController());
-        Consumer<AnimatorSet> splitAnimator = animatorSet ->
+        BiConsumer<AnimatorSet, Long> splitAnimator = (animatorSet, duration) ->
                 animatorSet.play(mSplitSelectStateController.getSplitAnimationController()
-                        .createPlaceholderDismissAnim(this, LAUNCHER_SPLIT_SELECTION_EXIT_HOME));
+                        .createPlaceholderDismissAnim(this, LAUNCHER_SPLIT_SELECTION_EXIT_HOME,
+                                duration));
         switch (mode) {
             case NO_BUTTON:
                 list.add(new NoButtonQuickSwitchTouchController(this));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 82a9c05..e8b5081 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -54,7 +54,7 @@
 import com.android.quickstep.util.OverviewToHomeAnim;
 import com.android.quickstep.views.RecentsView;
 
-import java.util.function.Consumer;
+import java.util.function.BiConsumer;
 
 /**
  * Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
@@ -67,7 +67,7 @@
     private static final float OVERVIEW_TO_HOME_SCRIM_MULTIPLIER = 0.5f;
 
     private final Launcher mLauncher;
-    private final Consumer<AnimatorSet> mCancelSplitRunnable;
+    private final BiConsumer<AnimatorSet, Long> mCancelSplitRunnable;
     private final SingleAxisSwipeDetector mSwipeDetector;
     private final float mPullbackDistance;
 
@@ -81,7 +81,7 @@
      *                            Animation should be added to the provided AnimatorSet
      */
     public NavBarToHomeTouchController(Launcher launcher,
-            Consumer<AnimatorSet> cancelSplitRunnable) {
+            BiConsumer<AnimatorSet, Long> cancelSplitRunnable) {
         mLauncher = launcher;
         mCancelSplitRunnable = cancelSplitRunnable;
         mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index fc3eeba..26e994f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -56,7 +56,7 @@
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 
-import java.util.function.Consumer;
+import java.util.function.BiConsumer;
 
 /**
  * Touch controller which handles swipe and hold from the nav bar to go to Overview. Swiping above
@@ -72,7 +72,7 @@
     private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
 
     private final VibratorWrapper mVibratorWrapper;
-    private final Consumer<AnimatorSet> mCancelSplitRunnable;
+    private final BiConsumer<AnimatorSet, Long> mCancelSplitRunnable;
     private final RecentsView mRecentsView;
     private final MotionPauseDetector mMotionPauseDetector;
     private final float mMotionPauseMinDisplacement;
@@ -93,7 +93,7 @@
      *                            Animation should be added to the provided AnimatorSet
      */
     public NoButtonNavbarToOverviewTouchController(Launcher l,
-            Consumer<AnimatorSet> cancelSplitRunnable) {
+            BiConsumer<AnimatorSet, Long> cancelSplitRunnable) {
         super(l);
         mRecentsView = l.getOverviewPanel();
         mMotionPauseDetector = new MotionPauseDetector(l);
@@ -208,9 +208,10 @@
             // Normally we compute the duration based on the velocity and distance to the given
             // state, but since the hint state tracks the entire screen without a clear endpoint, we
             // need to manually set the duration to a reasonable value.
-            animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher, true /* isToState */));
+            long duration = HINT_STATE.getTransitionDuration(mLauncher, true /* isToState */);
+            animator.setDuration(duration);
             AnimatorSet animatorSet = new AnimatorSet();
-            mCancelSplitRunnable.accept(animatorSet);
+            mCancelSplitRunnable.accept(animatorSet, duration);
             animatorSet.start();
         }
         if (FeatureFlags.ENABLE_PREMIUM_HAPTICS_ALL_APPS.get() &&
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index a1a3145..4e62d60 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -115,7 +115,6 @@
         ActiveGestureLog.INSTANCE.addLog(
                 /* event= */ "startRecentsAnimation",
                 /* gestureEvent= */ START_RECENTS_ANIMATION);
-        mRecentsAnimationStartPending = true;
         // Notify if recents animation is still running
         if (mController != null) {
             String msg = "New recents animation started before old animation completed";
@@ -300,10 +299,16 @@
                 options.setTransientLaunch();
             }
             options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
-            SystemUiProxy.INSTANCE.getNoCreate().startRecentsActivity(intent, options, mCallbacks);
+            mRecentsAnimationStartPending = SystemUiProxy.INSTANCE.getNoCreate()
+                    .startRecentsActivity(intent, options, mCallbacks);
         } else {
-            UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
-                    .startRecentsActivity(intent, eventTime, mCallbacks, null, null));
+            UI_HELPER_EXECUTOR.execute(
+                    () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
+                            intent,
+                            eventTime,
+                            mCallbacks,
+                            result -> mRecentsAnimationStartPending = result,
+                            MAIN_EXECUTOR.getHandler()));
         }
         gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED);
         return mCallbacks;
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 312cdc9..cc582d1 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -43,6 +43,7 @@
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.Snackbar;
 import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.views.GroupedTaskView;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskThumbnailView;
@@ -210,13 +211,19 @@
             }
         }
 
-        private void enterSplitSelect() {
+        protected void enterSplitSelect() {
             RecentsView overviewPanel = mThumbnailView.getTaskView().getRecentsView();
             // Task has already been dismissed
             if (overviewPanel == null) return;
             overviewPanel.initiateSplitSelect(mThumbnailView.getTaskView());
         }
 
+        protected void saveAppPair() {
+            GroupedTaskView taskView = (GroupedTaskView) mThumbnailView.getTaskView();
+            taskView.getRecentsView().getSplitSelectController().getAppPairsController()
+                    .saveAppPair(taskView);
+        }
+
         /**
          * Called when the overlay is no longer used.
          */
@@ -329,6 +336,10 @@
             public void onSplit() {
                 endLiveTileMode(TaskOverlay.this::enterSplitSelect);
             }
+
+            public void onSaveAppPair() {
+                endLiveTileMode(TaskOverlay.this::saveAppPair);
+            }
         }
     }
 
@@ -342,5 +353,8 @@
 
         /** User wants to start split screen with current app. */
         void onSplit();
+
+        /** User wants to save an app pair with current group of apps. */
+        void onSaveAppPair();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 9c84df8..c1b3a16 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -311,9 +311,21 @@
         @Override
         public List<SystemShortcut> getShortcuts(BaseDraggingActivity activity,
                 TaskIdAttributeContainer taskContainer) {
+            DeviceProfile deviceProfile = activity.getDeviceProfile();
             final TaskView taskView = taskContainer.getTaskView();
+            final RecentsView recentsView = taskView.getRecentsView();
+            boolean isLargeTileFocusedTask = deviceProfile.isTablet && taskView.isFocusedTask();
+            boolean isInExpectedScrollPosition =
+                    recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
+            boolean shouldShowActionsButtonInstead =
+                    isLargeTileFocusedTask && isInExpectedScrollPosition;
 
-            if (!FeatureFlags.enableAppPairs() || !taskView.containsMultipleTasks()) {
+            // No "save app pair" menu item if:
+            // - app pairs feature is not enabled
+            // - the task in question is a single task
+            // - the Overview Actions Button should be visible
+            if (!FeatureFlags.enableAppPairs() || !taskView.containsMultipleTasks()
+                    || shouldShowActionsButtonInstead) {
                 return null;
             }
 
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
index 6d9ecd9..132d1c1 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -30,7 +30,7 @@
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.states.StateAnimationConfig;
 
-import java.util.function.Consumer;
+import java.util.function.BiConsumer;
 
 /**
  * Runs an animation from overview to home. Currently, this animation is just a wrapper around the
@@ -44,14 +44,14 @@
     private final Launcher mLauncher;
     private final Runnable mOnReachedHome;
     @Nullable
-    private final Consumer<AnimatorSet> mSplitCancelConsumer;
+    private final BiConsumer<AnimatorSet, Long> mSplitCancelConsumer;
 
     // Only run mOnReachedHome when both of these are true.
     private boolean mIsHomeStaggeredAnimFinished;
     private boolean mIsOverviewHidden;
 
     public OverviewToHomeAnim(Launcher launcher, Runnable onReachedHome,
-            @Nullable Consumer<AnimatorSet> splitCancelConsumer) {
+            @Nullable BiConsumer<AnimatorSet, Long> splitCancelConsumer) {
         mLauncher = launcher;
         mOnReachedHome = onReachedHome;
         mSplitCancelConsumer = splitCancelConsumer;
@@ -103,7 +103,7 @@
 
         if (mSplitCancelConsumer != null) {
             // Clear split state when swiping to home
-            mSplitCancelConsumer.accept(anim);
+            mSplitCancelConsumer.accept(anim, config.duration);
         }
         anim.play(stateAnim);
         stateManager.setCurrentAnimation(anim, NORMAL);
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 1eb71fc..b7b1d8f 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -242,7 +242,7 @@
             return
         }
 
-        val anim = createPlaceholderDismissAnim(launcher, splitDismissEvent)
+        val anim = createPlaceholderDismissAnim(launcher, splitDismissEvent, null /*duration*/)
         anim.start()
     }
 
@@ -251,8 +251,10 @@
      * for why split is being dismissed
      */
     fun createPlaceholderDismissAnim(launcher: StatefulActivity<*>,
-                                     splitDismissEvent: EventEnum) : AnimatorSet {
+                                     splitDismissEvent: EventEnum,
+                                     duration: Long?) : AnimatorSet {
         val animatorSet = AnimatorSet()
+        duration?.let { animatorSet.duration = it }
         val recentsView : RecentsView<*, *> = launcher.getOverviewPanel()
         val floatingTask: FloatingTaskView = splitSelectStateController.firstFloatingTaskView
                 ?: return animatorSet
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 4c7d5c4..f2c9f27 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -28,7 +28,6 @@
 import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.ShapeDrawable;
 import android.graphics.drawable.shapes.RoundRectShape;
-import android.os.SystemProperties;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
@@ -52,7 +51,7 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.QuickStepContract;
-import com.android.wm.shell.Flags;
+import com.android.window.flags.Flags;
 
 import kotlin.Unit;
 
@@ -69,11 +68,6 @@
 // TODO(b/249371338): TaskView needs to be refactored to have better support for N tasks.
 public class DesktopTaskView extends TaskView {
 
-    private static final boolean DESKTOP_MODE_SUPPORTED = SystemProperties.getBoolean(
-            "persist.wm.debug.desktop_mode_2", false);
-
-    private static final boolean ENABLE_DESKTOP_WINDOWING = Flags.enableDesktopWindowing();
-
     private static final String TAG = DesktopTaskView.class.getSimpleName();
 
     private static final boolean DEBUG = false;
@@ -96,13 +90,7 @@
 
     /** Check whether desktop windowing is enabled */
     public static boolean isDesktopModeSupported() {
-        // Check for aconfig flag first
-        if (ENABLE_DESKTOP_WINDOWING) {
-            return true;
-        }
-        // Fall back to sysprop flag
-        // TODO(b/304778354): remove sysprop once desktop aconfig flag supports dynamic overriding
-        return DESKTOP_MODE_SUPPORTED;
+        return Flags.enableDesktopWindowingMode();
     }
 
     public DesktopTaskView(Context context) {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 10ef47c..259927d 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -24,6 +24,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.CancellableTask;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SplitConfigurationOptions;
@@ -40,11 +41,11 @@
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 
-import kotlin.Unit;
-
 import java.util.HashMap;
 import java.util.function.Consumer;
 
+import kotlin.Unit;
+
 /**
  * TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks
  *
@@ -382,7 +383,11 @@
 
     @Override
     public void setOverlayEnabled(boolean overlayEnabled) {
-        // Intentional no-op to prevent setting smart actions overlay on thumbnails
+        if (FeatureFlags.enableAppPairs()) {
+            super.setOverlayEnabled(overlayEnabled);
+        } else {
+            // Intentional no-op to prevent setting smart actions overlay on thumbnails
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 8281ad7..7a1c49a 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.Flags;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -92,14 +93,28 @@
     private static final int INDEX_SCROLL_ALPHA = 5;
     private static final int NUM_ALPHAS = 6;
 
+    public @interface ScreenshotButtonHiddenFlags { }
+    public static final int FLAG_MULTIPLE_TASKS_HIDE_SCREENSHOT = 1 << 0;
+
     public @interface SplitButtonHiddenFlags { }
-    public static final int FLAG_IS_NOT_TABLET = 1 << 0;
+    public static final int FLAG_SMALL_SCREEN_HIDE_SPLIT = 1 << 0;
+    public static final int FLAG_MULTIPLE_TASKS_HIDE_SPLIT = 1 << 1;
 
     public @interface SplitButtonDisabledFlags { }
-    public static final int FLAG_SINGLE_TASK = 1 << 0;
+    public static final int FLAG_SINGLE_TASK_DISABLE_SPLIT = 1 << 0;
+
+    public @interface AppPairButtonHiddenFlags { }
+    public static final int FLAG_SINGLE_TASK_HIDE_APP_PAIR = 1 << 0;
+    public static final int FLAG_SMALL_SCREEN_HIDE_APP_PAIR = 1 << 1;
 
     private MultiValueAlpha mMultiValueAlpha;
+
+    // The screenshot button is implemented as a Button in launcher3 and NexusLauncher, but is an
+    // ImageButton in go launcher (does not share a common class with Button). Take care when
+    // casting this.
+    private View mScreenshotButton;
     private Button mSplitButton;
+    private Button mSaveAppPairButton;
 
     @ActionsHiddenFlags
     private int mHiddenFlags;
@@ -107,11 +122,14 @@
     @ActionsDisabledFlags
     protected int mDisabledFlags;
 
+    @ScreenshotButtonHiddenFlags
+    private int mScreenshotButtonHiddenFlags;
+
     @SplitButtonHiddenFlags
     private int mSplitButtonHiddenFlags;
 
-    @SplitButtonDisabledFlags
-    private int mSplitButtonDisabledFlags;
+    @AppPairButtonHiddenFlags
+    private int mAppPairButtonHiddenFlags;
 
     @Nullable
     protected T mCallbacks;
@@ -138,9 +156,12 @@
         mMultiValueAlpha = new MultiValueAlpha(findViewById(R.id.action_buttons), NUM_ALPHAS);
         mMultiValueAlpha.setUpdateVisibility(true);
 
-        findViewById(R.id.action_screenshot).setOnClickListener(this);
+        mScreenshotButton = findViewById(R.id.action_screenshot);
+        mScreenshotButton.setOnClickListener(this);
         mSplitButton = findViewById(R.id.action_split);
         mSplitButton.setOnClickListener(this);
+        mSaveAppPairButton = findViewById(R.id.action_save_app_pair);
+        mSaveAppPairButton.setOnClickListener(this);
     }
 
     /**
@@ -162,6 +183,8 @@
             mCallbacks.onScreenshot();
         } else if (id == R.id.action_split) {
             mCallbacks.onSplit();
+        } else if (id == R.id.action_save_app_pair) {
+            mCallbacks.onSaveAppPair();
         }
     }
 
@@ -204,7 +227,49 @@
         }
         boolean isEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
         LayoutUtils.setViewEnabled(this, isEnabled);
-        updateSplitButtonEnabledState();
+    }
+
+    /**
+     * Updates a batch of flags to hide and show actions buttons when a grouped task (split screen)
+     * is focused.
+     * @param isGroupedTask True if the focused task is a grouped task.
+     */
+    public void updateForGroupedTask(boolean isGroupedTask) {
+        // Update flags to see if split button should be hidden.
+        updateSplitButtonHiddenFlags(FLAG_MULTIPLE_TASKS_HIDE_SPLIT, isGroupedTask);
+        // Update flags to see if screenshot button should be hidden.
+        updateScreenshotButtonHiddenFlags(FLAG_MULTIPLE_TASKS_HIDE_SCREENSHOT, isGroupedTask);
+        // Update flags to see if save app pair button should be hidden.
+        updateAppPairButtonHiddenFlags(FLAG_SINGLE_TASK_HIDE_APP_PAIR, !isGroupedTask);
+    }
+
+    /**
+     * Updates a batch of flags to hide and show actions buttons for tablet/non tablet case.
+     * @param isSmallScreen True if the current display is a small screen.
+     */
+    public void updateForSmallScreen(boolean isSmallScreen) {
+        // Update flags to see if split button should be hidden.
+        updateSplitButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_SPLIT, isSmallScreen);
+        // Update flags to see if save app pair button should be hidden.
+        updateAppPairButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_APP_PAIR, isSmallScreen);
+    }
+
+    /**
+     * Updates the proper flags to indicate whether the "Screenshot" button should be hidden.
+     *
+     * @param flag   The flag to update.
+     * @param enable Whether to enable the hidden flag: True will cause view to be hidden.
+     */
+    private void updateScreenshotButtonHiddenFlags(@ScreenshotButtonHiddenFlags int flag,
+            boolean enable) {
+        if (mScreenshotButton == null) return;
+        if (enable) {
+            mScreenshotButtonHiddenFlags |= flag;
+        } else {
+            mScreenshotButtonHiddenFlags &= ~flag;
+        }
+        int desiredVisibility = mScreenshotButtonHiddenFlags == 0 ? VISIBLE : GONE;
+        mScreenshotButton.setVisibility(desiredVisibility);
     }
 
     /**
@@ -213,16 +278,17 @@
      * @param flag   The flag to update.
      * @param enable Whether to enable the hidden flag: True will cause view to be hidden.
      */
-    public void updateSplitButtonHiddenFlags(@SplitButtonHiddenFlags int flag, boolean enable) {
+    void updateSplitButtonHiddenFlags(@SplitButtonHiddenFlags int flag,
+            boolean enable) {
+        if (mSplitButton == null) return;
         if (enable) {
             mSplitButtonHiddenFlags |= flag;
         } else {
             mSplitButtonHiddenFlags &= ~flag;
         }
-        if (mSplitButton == null) return;
-        boolean shouldBeVisible = mSplitButtonHiddenFlags == 0;
-        mSplitButton.setVisibility(shouldBeVisible ? VISIBLE : GONE);
-        findViewById(R.id.action_split_space).setVisibility(shouldBeVisible ? VISIBLE : GONE);
+        int desiredVisibility = mSplitButtonHiddenFlags == 0 ? VISIBLE : GONE;
+        mSplitButton.setVisibility(desiredVisibility);
+        findViewById(R.id.action_split_space).setVisibility(desiredVisibility);
 
         String callStack = Arrays.stream(
                         Log.getStackTraceString(new Exception("thread stacktrace"))
@@ -232,23 +298,30 @@
                 .collect(Collectors.joining("\n"));
         Log.d("b/321291049", "updateSplitButtonHiddenFlags called with flag: " + flag
                 + " enabled: " + enable
-                + " shouldBeVisible: " + shouldBeVisible
+                + " visibility: " + desiredVisibility
                 + " partial trace: \n" + callStack);
     }
 
     /**
-     * Updates the proper flags to indicate whether the "Split screen" button should be disabled.
+     * Updates the proper flags to indicate whether the "Save app pair" button should be disabled.
      *
      * @param flag   The flag to update.
-     * @param enable Whether to enable the disable flag: True will cause view to be disabled.
+     * @param enable Whether to enable the hidden flag: True will cause view to be hidden.
      */
-    public void updateSplitButtonDisabledFlags(@SplitButtonDisabledFlags int flag, boolean enable) {
-        if (enable) {
-            mSplitButtonDisabledFlags |= flag;
-        } else {
-            mSplitButtonDisabledFlags &= ~flag;
+    private void updateAppPairButtonHiddenFlags(
+            @AppPairButtonHiddenFlags int flag, boolean enable) {
+        if (!FeatureFlags.enableAppPairs()) {
+            return;
         }
-        updateSplitButtonEnabledState();
+
+        if (mSaveAppPairButton == null) return;
+        if (enable) {
+            mAppPairButtonHiddenFlags |= flag;
+        } else {
+            mAppPairButtonHiddenFlags &= ~flag;
+        }
+        int desiredVisibility = mAppPairButtonHiddenFlags == 0 ? VISIBLE : GONE;
+        mSaveAppPairButton.setVisibility(desiredVisibility);
     }
 
     public MultiProperty getContentAlpha() {
@@ -326,19 +399,7 @@
                 ? R.drawable.ic_split_horizontal
                 : R.drawable.ic_split_vertical;
         mSplitButton.setCompoundDrawablesRelativeWithIntrinsicBounds(splitIconRes, 0, 0, 0);
+        mSaveAppPairButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
+                R.drawable.ic_save_app_pair, 0, 0, 0);
     }
-
-    /**
-     * Enables/disables the "Split" button based on the status of mSplitButtonDisabledFlags and
-     * mDisabledFlags.
-     */
-    private void updateSplitButtonEnabledState() {
-        if (mSplitButton == null) {
-            return;
-        }
-        boolean isParentEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
-        boolean shouldBeEnabled = mSplitButtonDisabledFlags == 0 && isParentEnabled;
-        mSplitButton.setEnabled(shouldBeEnabled);
-    }
-
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 7bfa5ed..6699147 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -62,8 +62,6 @@
 import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_UP;
 import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
 import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
-import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET;
-import static com.android.quickstep.views.OverviewActionsView.FLAG_SINGLE_TASK;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_ACTIONS_IN_MENU;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_DESKTOP;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
@@ -493,9 +491,10 @@
     private final Rect mClearAllButtonDeadZoneRect = new Rect();
     private final Rect mTaskViewDeadZoneRect = new Rect();
     /**
-     * Reflects if Recents is currently in the middle of a gesture
+     * Reflects if Recents is currently in the middle of a gesture, and if so, which tasks are
+     * running. If a gesture is not in progress, this will be null.
      */
-    private boolean mGestureActive;
+    private @Nullable Task[] mActiveGestureRunningTasks;
 
     // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
     private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
@@ -1841,7 +1840,15 @@
             if (newRunningTaskView != null) {
                 mRunningTaskViewId = newRunningTaskView.getTaskViewId();
             } else {
-                mRunningTaskViewId = INVALID_TASK_ID;
+                if (mActiveGestureRunningTasks != null) {
+                    // This will update mRunningTaskViewId and create a stub view if necessary.
+                    // We try to avoid this because it can cause a scroll jump, but it is needed
+                    // for cases where the running task isn't included in this load plan (e.g. if
+                    // the current running task is excludedFromRecents.)
+                    showCurrentTask(mActiveGestureRunningTasks);
+                } else {
+                    mRunningTaskViewId = INVALID_TASK_ID;
+                }
             }
         }
 
@@ -1909,10 +1916,17 @@
     }
 
     private void removeTasksViewsAndClearAllButton() {
+        // This handles an edge case where applyLoadPlan happens during a gesture when the
+        // only Task is one with excludeFromRecents, in which case we should not remove it.
+        final int stubRunningTaskIndex = isGestureActive() ? getRunningTaskIndex() : -1;
+
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
+            if (i == stubRunningTaskIndex) {
+                continue;
+            }
             removeView(requireTaskViewAt(i));
         }
-        if (indexOfChild(mClearAllButton) != -1) {
+        if (getTaskViewCount() == 0 && indexOfChild(mClearAllButton) != -1) {
             removeView(mClearAllButton);
         }
     }
@@ -2375,7 +2389,7 @@
                         // Ignore thumbnail update if it's current running task during the gesture
                         // We snapshot at end of gesture, it will update then
                         int changes = dataChanges;
-                        if (taskView == getRunningTaskView() && mGestureActive) {
+                        if (taskView == getRunningTaskView() && isGestureActive()) {
                             changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL;
                         }
                         taskView.onTaskListVisibilityChanged(true /* visible */, changes);
@@ -2587,7 +2601,7 @@
      */
     public void onGestureAnimationStart(
             Task[] runningTasks, RotationTouchHelper rotationTouchHelper) {
-        mGestureActive = true;
+        mActiveGestureRunningTasks = runningTasks;
         // This needs to be called before the other states are set since it can create the task view
         if (mOrientationState.setGestureActive(true)) {
             setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
@@ -2597,13 +2611,17 @@
             updateSizeAndPadding();
         }
 
-        showCurrentTask(runningTasks);
+        showCurrentTask(mActiveGestureRunningTasks);
         setEnableFreeScroll(false);
         setEnableDrawingLiveTile(false);
         setRunningTaskHidden(true);
         setTaskIconScaledDown(true);
     }
 
+    private boolean isGestureActive() {
+        return mActiveGestureRunningTasks != null;
+    }
+
     /**
      * Called only when a swipe-up gesture from an app has completed. Only called after
      * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
@@ -2712,7 +2730,7 @@
      * Called when a gesture from an app has finished, and the animation to the target has ended.
      */
     public void onGestureAnimationEnd() {
-        mGestureActive = false;
+        mActiveGestureRunningTasks = null;
         if (mOrientationState.setGestureActive(false)) {
             updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false);
         }
@@ -3987,18 +4005,24 @@
     }
 
     /**
-     * Hides all overview actions if current page is for split apps, shows otherwise
-     * If actions are showing, we only show split option if
+     * Hides all overview actions if user is halfway through split selection, shows otherwise.
+     * We only show split option if:
+     * * Focused view is a single app
      * * Device is large screen
-     * * There are at least 2 tasks to invoke split
      */
     private void updateCurrentTaskActionsVisibility() {
         boolean isCurrentSplit = getCurrentPageTaskView() instanceof GroupedTaskView;
-        mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
+        // Update flags to see if entire actions bar should be hidden.
+        if (!FeatureFlags.enableAppPairs()) {
+            mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
+        }
         mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
-        mActionsView.updateSplitButtonHiddenFlags(FLAG_IS_NOT_TABLET,
-                !mActivity.getDeviceProfile().isTablet);
-        mActionsView.updateSplitButtonDisabledFlags(FLAG_SINGLE_TASK, /*enable=*/ false);
+        // Update flags to see if actions bar should show buttons for a single task or a pair of
+        // tasks.
+        mActionsView.updateForGroupedTask(isCurrentSplit);
+        // Update flags to see if actions bar should show buttons for tablets or phones.
+        mActionsView.updateForSmallScreen(!mActivity.getDeviceProfile().isTablet);
+
         if (isDesktopModeSupported()) {
             boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
             mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index a11a913..be07e8a 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -21,11 +21,9 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
-import android.os.Bundle;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -141,32 +139,6 @@
         mLauncher.getStateManager().goToState(LauncherState.NORMAL);
     }
 
-    @Override
-    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfo(info);
-        if (!FeatureFlags.enableSplitContextually()) {
-            return;
-        }
-
-        info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
-                R.string.toast_split_select_cont_desc,
-                getResources().getString(R.string.toast_split_select_cont_desc)
-        ));
-    }
-
-    @Override
-    public boolean performAccessibilityAction(int action, Bundle arguments) {
-        if (!FeatureFlags.enableSplitContextually()) {
-            return super.performAccessibilityAction(action, arguments);
-        }
-
-        if (action == R.string.toast_split_select_cont_desc) {
-            exitSplitSelection();
-            return true;
-        }
-        return super.performAccessibilityAction(action, arguments);
-    }
-
     void ensureProperRotation() {
         ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler()
                 .setSplitInstructionsParams(
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 2826a8f..2bd48d5 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -18,6 +18,8 @@
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.launcher3.Flags.enableOverviewIconMenu;
+import static com.android.launcher3.testing.shared.TestProtocol.TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE;
+import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
@@ -370,6 +372,8 @@
         mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationStart(Animator animation) {
+                testLogD(TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE,
+                        "TaskMenuView.java.animateOpenOrClosed: onAnimationStart");
                 setVisibility(VISIBLE);
                 if (closing && mOnClosingStartCallback != null) {
                     mOnClosingStartCallback.run();
@@ -378,6 +382,8 @@
 
             @Override
             public void onAnimationSuccess(Animator animator) {
+                testLogD(TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE,
+                        "TaskMenuView.java.animateOpenOrClosed: onAnimationSuccess");
                 if (closing) {
                     closeComplete();
                 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 5bcf72a..45a9527 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -503,6 +503,7 @@
 
     @Test
     @PortraitLandscape
+    @ScreenRecord // b/326839375
     public void testOverviewDeadzones() throws Exception {
         startTestAppsWithCheck();
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5071978..a912e2d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -31,6 +31,7 @@
     <!-- Hotseat -->
     <dimen name="dynamic_grid_hotseat_bottom_tall_padding">0dp</dimen>
     <dimen name="spring_loaded_hotseat_top_margin">76dp</dimen>
+    <dimen name="spring_loaded_hotseat_top_margin_wide_portrait">64dp</dimen>
 
     <dimen name="dynamic_grid_hotseat_side_padding">0dp</dimen>
 
@@ -47,6 +48,7 @@
     <dimen name="drop_target_vertical_gap">20dp</dimen>
     <dimen name="drop_target_top_margin">32dp</dimen>
     <dimen name="drop_target_bottom_margin">16dp</dimen>
+    <dimen name="drop_target_bottom_margin_wide_portrait">32dp</dimen>
 
     <!-- App Widget resize frame -->
     <!-- Button drop target bar -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fa7057f..aaef15b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -47,9 +47,9 @@
     <!-- App pair default title -->
     <string name="app_pair_default_title"><xliff:g id="app1" example="Chrome">%1$s</xliff:g> | <xliff:g id="app2" example="YouTube">%2$s</xliff:g></string>
     <!-- Displayed when an app pair can't launch at this screen size [CHAR_LIMIT=none] -->
-    <string name="app_pair_unlaunchable_at_screen_size">This pair isn\'t supported at this screen size</string>
+    <string name="app_pair_unlaunchable_at_screen_size">This app pair isn\'t supported on this device</string>
     <!-- Displayed when an app pair can't launch at this screen size, but user can unfold device to restore functionality [CHAR_LIMIT=none] -->
-    <string name="app_pair_needs_unfold">Unfold your device to use this pair</string>
+    <string name="app_pair_needs_unfold">Unfold device to use this app pair</string>
 
     <!-- Widgets -->
     <!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
@@ -326,6 +326,8 @@
     <string name="app_downloading_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> downloading, <xliff:g id="progress" example="30%">%2$s</xliff:g> complete</string>
     <!-- Title for an app whose download has been started. -->
     <string name="app_waiting_download_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> waiting to install</string>
+    <!-- Title for an app which is archived. -->
+    <string name="app_archived_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> is archived. Tap to download.</string>
 
 
     <!-- Title shown on the alert dialog prompting the user to update the application in market
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index acfc5e4..1049314 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.testing.shared.TestProtocol.ACTIVITY_NOT_RESUMED_AFTER_BACK;
-import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing;
 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
@@ -310,9 +308,6 @@
      * Sets the activity to appear as paused.
      */
     public void setPaused() {
-        if (sDebugTracing) {
-            Log.d(ACTIVITY_NOT_RESUMED_AFTER_BACK, "Activity setPaused: " + this, new Throwable());
-        }
         removeActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_DEFERRED_RESUMED);
     }
 
@@ -320,7 +315,6 @@
      * Sets the activity to appear as resumed.
      */
     public void setResumed() {
-        if (sDebugTracing) Log.d(ACTIVITY_NOT_RESUMED_AFTER_BACK, "Activity setResumed: " + this);
         addActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE);
         removeActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
     }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 259ddbd..4f071fe 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -1010,7 +1010,9 @@
     }
 
     private void setDownloadStateContentDescription(ItemInfoWithIcon info, int progressLevel) {
-        if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK)
+        if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_ARCHIVED) != 0 && progressLevel == 0) {
+            setContentDescription(getContext().getString(R.string.app_archived_title, info.title));
+        } else if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK)
                 != 0) {
             String percentageString = NumberFormat.getPercentInstance()
                     .format(progressLevel * 0.01);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 4dd2fe3..4b4bdc2 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -83,6 +83,7 @@
 
     // Minimum aspect ratio beyond which an extra top padding may be applied to a bottom sheet.
     private static final float MIN_ASPECT_RATIO_FOR_EXTRA_TOP_PADDING = 1.5f;
+    private static final float MAX_ASPECT_RATIO_FOR_ALTERNATE_EDIT_STATE = 1.5f;
 
     public static final PointF DEFAULT_SCALE = new PointF(1.0f, 1.0f);
     public static final ViewScaleProvider DEFAULT_PROVIDER = itemInfo -> DEFAULT_SCALE;
@@ -503,8 +504,17 @@
         }
 
         dropTargetBarSizePx = res.getDimensionPixelSize(R.dimen.dynamic_grid_drop_target_size);
-        dropTargetBarTopMarginPx = res.getDimensionPixelSize(R.dimen.drop_target_top_margin);
-        dropTargetBarBottomMarginPx = res.getDimensionPixelSize(R.dimen.drop_target_bottom_margin);
+        // Some foldable portrait modes are too wide in terms of aspect ratio so we need to tweak
+        // the dimensions for edit state.
+        final boolean shouldApplyWidePortraitDimens = isTablet
+                && !isLandscape
+                && aspectRatio < MAX_ASPECT_RATIO_FOR_ALTERNATE_EDIT_STATE;
+        dropTargetBarTopMarginPx = shouldApplyWidePortraitDimens
+                ? 0
+                : res.getDimensionPixelSize(R.dimen.drop_target_top_margin);
+        dropTargetBarBottomMarginPx = shouldApplyWidePortraitDimens
+                ? res.getDimensionPixelSize(R.dimen.drop_target_bottom_margin_wide_portrait)
+                : res.getDimensionPixelSize(R.dimen.drop_target_bottom_margin);
         dropTargetDragPaddingPx = res.getDimensionPixelSize(R.dimen.drop_target_drag_padding);
         dropTargetTextSizePx = res.getDimensionPixelSize(R.dimen.drop_target_text_size);
         dropTargetHorizontalPaddingPx = res.getDimensionPixelSize(
@@ -595,8 +605,9 @@
             }
         }
 
-        springLoadedHotseatBarTopMarginPx = res.getDimensionPixelSize(
-                R.dimen.spring_loaded_hotseat_top_margin);
+        springLoadedHotseatBarTopMarginPx = shouldApplyWidePortraitDimens
+                ? res.getDimensionPixelSize(R.dimen.spring_loaded_hotseat_top_margin_wide_portrait)
+                : res.getDimensionPixelSize(R.dimen.spring_loaded_hotseat_top_margin);
 
         if (mIsResponsiveGrid) {
             updateHotseatSizes(mResponsiveWorkspaceCellSpec.getIconSize());
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1ab6222..c1ebbe5 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -263,7 +263,7 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.shared.LauncherOverlayManager;
 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayTouchProxy;
-import com.android.wm.shell.Flags;
+import com.android.window.flags.Flags;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -319,10 +319,6 @@
     private static final FloatProperty<Hotseat> HOTSEAT_WIDGET_SCALE =
             HOTSEAT_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WIDGET_TRANSITION);
 
-    private static final boolean ENABLE_DESKTOP_WINDOWING = Flags.enableDesktopWindowing();
-    private static final boolean DESKTOP_MODE_SUPPORTED =
-            "1".equals(Utilities.getSystemProperty("persist.wm.debug.desktop_mode_2", "0"));
-
     private final ModelCallbacks mModelCallbacks = createModelCallbacks();
 
     private final KeyboardShortcutsDelegate mKeyboardShortcutsDelegate =
@@ -2724,8 +2720,7 @@
     }
 
     private void updateDisallowBack() {
-        // TODO(b/304778354): remove sysprop once desktop aconfig flag supports dynamic overriding
-        if (ENABLE_DESKTOP_WINDOWING || DESKTOP_MODE_SUPPORTED) {
+        if (Flags.enableDesktopWindowingMode()) {
             // Do not disable back in launcher when prototype behavior is enabled
             return;
         }
diff --git a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
index 91fcf80..6067454 100644
--- a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
+++ b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.allapps;
 
 import static android.view.View.GONE;
+import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
@@ -49,6 +50,7 @@
 import com.android.launcher3.allapps.UserProfileManager.UserProfileState;
 import com.android.launcher3.anim.AnimatedPropertySetter;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.views.RecyclerViewFastScroller;
 
 import java.util.List;
 
@@ -220,11 +222,15 @@
     private ValueAnimator animateCollapseAnimation(ViewGroup lockButton) {
         float from = 1;
         float to = 0;
+        RecyclerViewFastScroller scrollBar = mAllApps.getActiveRecyclerView().getScrollbar();
         ValueAnimator collapseAnim = ValueAnimator.ofFloat(from, to);
         collapseAnim.setDuration(EXPAND_COLLAPSE_DURATION);
         collapseAnim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
+                if (scrollBar != null) {
+                    scrollBar.setVisibility(INVISIBLE);
+                }
                 // scroll up
                 collapse();
                 // Animate the collapsing of the text.
@@ -233,6 +239,10 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
+                if (scrollBar != null) {
+                    scrollBar.setThumbOffsetY(-1);
+                    scrollBar.setVisibility(VISIBLE);
+                }
                 mPrivateProfileManager.lockPrivateProfile();
             }
         });
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 1231cd7..07df7af 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
 import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
+import static com.android.launcher3.config.FeatureFlags.enableAppPairs;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.app.Activity;
@@ -300,6 +301,11 @@
                 return response;
             }
 
+            case TestProtocol.REQUEST_FLAG_ENABLE_APP_PAIRS: {
+                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, enableAppPairs());
+                return response;
+            }
+
             default:
                 return null;
         }
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index f4a0225..837d7bc 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -169,6 +169,15 @@
             dividerWidthPercent = visualDividerBounds.width() / totalWidth;
             dividerHeightPercent = visualDividerBounds.height() / totalHeight;
         }
+
+        @Override
+        public String toString() {
+            return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n"
+                    + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId +  "\n"
+                    + "Divider: " + visualDividerBounds + "\n"
+                    + "AppsVertical? " + appsStackedVertically + "\n"
+                    + "snapPosition: " + snapPosition;
+        }
     }
 
     public static class SplitStageInfo {
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
index 93ab6f2..3169f41 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
@@ -79,7 +79,7 @@
 	hotseatBarEndOffset: 0.0px (0.0dp)
 	hotseatQsbSpace: 0.0px (0.0dp)
 	hotseatQsbHeight: 0.0px (0.0dp)
-	springLoadedHotseatBarTopMarginPx: 171.0px (65.14286dp)
+	springLoadedHotseatBarTopMarginPx: 168.0px (64.0dp)
 	getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
 	getHotseatLayoutPadding(context).bottom: 108.0px (41.142857dp)
 	getHotseatLayoutPadding(context).left: 98.0px (37.333332dp)
@@ -118,12 +118,12 @@
 	overviewPageSpacing: 0.0px (0.0dp)
 	overviewRowSpacing: 0.0px (0.0dp)
 	overviewGridSideMargin: 0.0px (0.0dp)
-	dropTargetBarTopMarginPx: 168.0px (64.0dp)
+	dropTargetBarTopMarginPx: 0.0px (0.0dp)
 	dropTargetBarSizePx: 147.0px (56.0dp)
-	dropTargetBarBottomMarginPx: 42.0px (16.0dp)
-	getCellLayoutSpringLoadShrunkTop(): 490.0px (186.66667dp)
-	getCellLayoutSpringLoadShrunkBottom(): 1770.0px (674.2857dp)
+	dropTargetBarBottomMarginPx: 84.0px (32.0dp)
+	getCellLayoutSpringLoadShrunkTop(): 364.0px (138.66667dp)
+	getCellLayoutSpringLoadShrunkBottom(): 1773.0px (675.4286dp)
 	workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
-	getWorkspaceSpringLoadScale(): 0.7437536px (0.2833347dp)
+	getWorkspaceSpringLoadScale(): 0.81871px (0.31188953dp)
 	getCellLayoutHeight(): 1721.0px (655.619dp)
 	getCellLayoutWidth(): 899.0px (342.4762dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
index ec9a10e..9d3d7bc 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
@@ -79,7 +79,7 @@
 	hotseatBarEndOffset: 0.0px (0.0dp)
 	hotseatQsbSpace: 0.0px (0.0dp)
 	hotseatQsbHeight: 0.0px (0.0dp)
-	springLoadedHotseatBarTopMarginPx: 171.0px (65.14286dp)
+	springLoadedHotseatBarTopMarginPx: 168.0px (64.0dp)
 	getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
 	getHotseatLayoutPadding(context).bottom: 108.0px (41.142857dp)
 	getHotseatLayoutPadding(context).left: 98.0px (37.333332dp)
@@ -118,12 +118,12 @@
 	overviewPageSpacing: 0.0px (0.0dp)
 	overviewRowSpacing: 0.0px (0.0dp)
 	overviewGridSideMargin: 0.0px (0.0dp)
-	dropTargetBarTopMarginPx: 168.0px (64.0dp)
+	dropTargetBarTopMarginPx: 0.0px (0.0dp)
 	dropTargetBarSizePx: 147.0px (56.0dp)
-	dropTargetBarBottomMarginPx: 42.0px (16.0dp)
-	getCellLayoutSpringLoadShrunkTop(): 490.0px (186.66667dp)
-	getCellLayoutSpringLoadShrunkBottom(): 1770.0px (674.2857dp)
+	dropTargetBarBottomMarginPx: 84.0px (32.0dp)
+	getCellLayoutSpringLoadShrunkTop(): 364.0px (138.66667dp)
+	getCellLayoutSpringLoadShrunkBottom(): 1773.0px (675.4286dp)
 	workspaceSpringLoadedMinNextPageVisiblePx: 63.0px (24.0dp)
-	getWorkspaceSpringLoadScale(): 0.7437536px (0.2833347dp)
+	getWorkspaceSpringLoadScale(): 0.81871px (0.31188953dp)
 	getCellLayoutHeight(): 1721.0px (655.619dp)
 	getCellLayoutWidth(): 899.0px (342.4762dp)
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 77dd8d4..7d195fd 100644
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -172,8 +172,10 @@
     public static final String ICON_MISSING = "b/282963545";
     public static final String OVERVIEW_OVER_HOME = "b/279059025";
     public static final String UIOBJECT_STALE_ELEMENT = "b/319501259";
-    public static final String ACTIVITY_NOT_RESUMED_AFTER_BACK = "b/322823209";
     public static final String GET_FROM_RECENTS_FAILURE = "b/321775748";
+    public static final String SUCCESSFUL_GESTURE_MISMATCH_EVENTS = "b/324940434";
+    public static final String TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE = "b/326908466";
+    public static final String TEST_TAPL_OVERVIEW_ACTIONS_MENU_FAILURE = "b/326073471";
 
     public static final String REQUEST_EMULATE_DISPLAY = "emulate-display";
     public static final String REQUEST_STOP_EMULATE_DISPLAY = "stop-emulate-display";
@@ -181,6 +183,7 @@
     public static final String REQUEST_EMULATE_PRINT_DEVICE = "emulate-print-device";
 
     public static final String REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW = "enable-grid-only-overview";
+    public static final String REQUEST_FLAG_ENABLE_APP_PAIRS = "enable-app-pairs";
 
     public static final String REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED =
             "unstash-bubble-bar-if-stashed";
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/AllApps.java
index 2dbc29d..9591891 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -261,7 +261,7 @@
             int attempts = 0;
             final Rect margins = new Rect(
                     /* left= */ 0,
-                    getTopVisibleIconBounds(allAppsContainer).bottom,
+                    mHeight / 2,
                     /* right= */ 0,
                     /* bottom= */ getAppsListRecyclerBottomPadding());
 
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/AppIcon.java
index 156568b..7c6d684 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.tapl;
 
+import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
+
+import android.util.Log;
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
@@ -49,7 +52,7 @@
     /**
      * Find an app icon with the given name.
      *
-     * @param appName app icon to look for
+     * @param appName  app icon to look for
      * @param launcher (optional) - only match ui elements from Launcher's package
      */
     static BySelector getAppIconSelector(String appName, LauncherInstrumentation launcher) {
@@ -95,6 +98,8 @@
 
     @Override
     protected void waitForLongPressConfirmation() {
+        Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
+                "AppIcon.waitForLongPressConfirmation, resName: popupContainer");
         mLauncher.waitForLauncherObject("popup_container");
     }
 
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/BaseOverview.java
index ad95ecf..4f20c57 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -401,8 +401,11 @@
         if (isTablet && Math.abs(task.getExactCenterX() - mLauncher.getExactScreenCenterX()) >= 1) {
             return false;
         }
-        // Overview actions aren't visible for split screen tasks.
-        return !task.isTaskSplit();
+        if (!mLauncher.isAppPairsEnabled() && task.isTaskSplit()) {
+            // Overview actions aren't visible for split screen tasks.
+            return false;
+        }
+        return true;
     }
 
     /**
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/Launchable.java
index ed47334..9d3bc6e 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -17,8 +17,10 @@
 package com.android.launcher3.tapl;
 
 import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
 
 import android.graphics.Point;
+import android.util.Log;
 import android.view.MotionEvent;
 
 import androidx.test.uiautomator.UiObject2;
@@ -113,11 +115,15 @@
                 iconCenter.y - getStartDragThreshold());
 
         if (runToSpringLoadedState) {
+            Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
+                    "Launchable.startDrag: actionName: long-pressing and triggering drag start"
+                            + " iconCenter: " + iconCenter + " dragStartCenter: "
+                            + dragStartCenter);
             mLauncher.runToState(() -> movePointerForStartDrag(
-                    downTime,
-                    iconCenter,
-                    dragStartCenter,
-                    expectLongClickEvents),
+                            downTime,
+                            iconCenter,
+                            dragStartCenter,
+                            expectLongClickEvents),
                     SPRING_LOADED_STATE_ORDINAL, "long-pressing and triggering drag start");
         } else {
             movePointerForStartDrag(
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index 200f2ff..b5414b7 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -24,7 +24,9 @@
 import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_SHELL_DRAG_READY;
 import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_STASHED_TASKBAR_SCALE;
 import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_TASKBAR_FROM_NAV_THRESHOLD;
+import static com.android.launcher3.testing.shared.TestProtocol.SUCCESSFUL_GESTURE_MISMATCH_EVENTS;
 import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
+import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
 
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -139,6 +141,8 @@
 
             return new Taskbar(mLauncher);
         } finally {
+            testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
+                    "swipeUpToUnstashTaskbar: completed gesture");
             mLauncher.getTestInfo(REQUEST_DISABLE_BLOCK_TIMEOUT);
         }
     }
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 825bb2b..aa792bf 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -30,7 +30,10 @@
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_GET_SPLIT_SELECTION_ACTIVE;
 import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_NUM_ALL_APPS_COLUMNS;
+import static com.android.launcher3.testing.shared.TestProtocol.SUCCESSFUL_GESTURE_MISMATCH_EVENTS;
+import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
 import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
+import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
 
 import android.app.ActivityManager;
 import android.app.Instrumentation;
@@ -1163,7 +1166,11 @@
                 log("Hierarchy before clicking home:");
                 dumpViewHierarchy();
                 action = "clicking home button";
-
+                Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
+                        "LauncherInstrumentation.goHome: isThreeFingerTrackpadGesture: "
+                                + isThreeFingerTrackpadGesture
+                                + "getNavigationModel() == NavigationModel.ZERO_BUTTON: " + (
+                                getNavigationModel() == NavigationModel.ZERO_BUTTON));
                 runToState(
                         getHomeButton()::click,
                         NORMAL_STATE_ORDINAL,
@@ -1510,6 +1517,8 @@
 
     @NonNull
     UiObject2 waitForLauncherObject(String resName) {
+        Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
+                "LauncherInstrumentation.waitForLauncherObject");
         return waitForObjectBySelector(getLauncherObjectSelector(resName));
     }
 
@@ -1539,12 +1548,16 @@
 
     @NonNull
     List<UiObject2> waitForObjectsBySelector(BySelector selector) {
+        Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
+                "LauncherInstrumentation.waitForObjectsBySelector");
         final List<UiObject2> objects = mDevice.wait(Until.findObjects(selector), WAIT_TIME_MS);
         assertNotNull("Can't find any view in Launcher, selector: " + selector, objects);
         return objects;
     }
 
     private UiObject2 waitForObjectBySelector(BySelector selector) {
+        Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
+                "LauncherInstrumentation.waitForObjectBySelector");
         final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
         assertNotNull("Can't find a view in Launcher, selector: " + selector, object);
         return object;
@@ -1587,6 +1600,9 @@
 
     void runToState(Runnable command, int expectedState, boolean requireEvent, String actionName) {
         if (requireEvent) {
+            Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
+                    "LauncherInstrumentation.runToState: command: " + command + " expectedState: "
+                            + expectedState + " actionName: " + actionName + "requireEvent: true");
             runToState(command, expectedState, actionName);
         } else {
             command.run();
@@ -1936,6 +1952,10 @@
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
+    boolean isAppPairsEnabled() {
+        return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_APP_PAIRS).getBoolean(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
     public void sendPointer(long downTime, long currentTime, int action, Point point,
             GestureScope gestureScope) {
         sendPointer(downTime, currentTime, action, point, gestureScope,
@@ -1964,11 +1984,15 @@
                     mPointerCount = 1;
                     pointerCount = mPointerCount;
                 }
+                Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
+                        "LauncherInstrumentation.sendPointer: ACTION_DOWN");
                 break;
             case MotionEvent.ACTION_UP:
                 if (hasTIS && gestureScope == GestureScope.EXPECT_PILFER) {
                     expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS);
                 }
+                Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
+                        "LauncherInstrumentation.sendPointer: ACTION_UP");
                 break;
             case MotionEvent.ACTION_POINTER_DOWN:
                 mPointerCount++;
@@ -2309,9 +2333,13 @@
             }
 
             if (mEventChecker != null) {
+                testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS, "eventsCheck: mEventChecker exists");
                 mEventChecker = null;
                 if (mCheckEventsForSuccessfulGestures) {
                     final String message = eventChecker.verify(WAIT_TIME_MS, true);
+                    testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS,
+                            "mCheckEventsForSuccessfulGestures = true | eventsCheck: message="
+                                    + message);
                     if (message != null) {
                         dumpDiagnostics(message);
                         checkForAnomaly();
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LogEventChecker.java
index 672c6e0..70d63bd 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/LogEventChecker.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -18,6 +18,8 @@
 import static com.android.launcher3.testing.shared.TestProtocol.SEQUENCE_MAIN;
 import static com.android.launcher3.testing.shared.TestProtocol.SEQUENCE_PILFER;
 import static com.android.launcher3.testing.shared.TestProtocol.SEQUENCE_TIS;
+import static com.android.launcher3.testing.shared.TestProtocol.SUCCESSFUL_GESTURE_MISMATCH_EVENTS;
+import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
 
 import android.os.SystemClock;
 
@@ -88,6 +90,7 @@
     }
 
     String verify(long waitForExpectedCountMs, boolean successfulGesture) {
+        testLogD(SUCCESSFUL_GESTURE_MISMATCH_EVENTS, "LogEventChecker.java - verify");
         final ListMap<String> actualEvents = finishSync(waitForExpectedCountMs);
         if (actualEvents == null) return "null event sequences because launcher likely died";
 
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/Workspace.java
index 506e563..4e92634 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -25,6 +25,7 @@
 import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
 import static com.android.launcher3.testing.shared.TestProtocol.UIOBJECT_STALE_ELEMENT;
 
 import static junit.framework.TestCase.assertNotNull;
@@ -626,6 +627,8 @@
         try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer(
                 "want to drag icon to workspace")) {
             final long downTime = SystemClock.uptimeMillis();
+            Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
+                    "Workspace.dragIconToWorkspace: starting drag | downtime: " + downTime);
             Point dragStart = launchable.startDrag(
                     downTime,
                     expectLongClickEvents,
diff --git a/tests/multivalentTests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java b/tests/multivalentTests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
index e5a2a2e..b42d43b 100644
--- a/tests/multivalentTests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
+++ b/tests/multivalentTests/tapl/com/android/launcher3/tapl/WorkspaceDragSource.java
@@ -15,7 +15,10 @@
  */
 package com.android.launcher3.tapl;
 
+import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
+
 import android.graphics.Point;
+import android.util.Log;
 
 import java.util.function.Supplier;
 
@@ -76,6 +79,9 @@
              LauncherInstrumentation.Closable c = launcher.addContextLayer(
                      String.format("want to drag the icon to cell(%d, %d)", cellX, cellY))) {
             final Supplier<Point> dest = () -> Workspace.getCellCenter(launcher, cellX, cellY);
+            Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
+                    "WorkspaceDragSource.dragToWorkspace: dragging icon to workspace | dest: "
+                            + dest.get());
             Workspace.dragIconToWorkspace(
                     launcher,
                     launchable,
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index 615f679..d1227d8 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.dragging;
 
+import static com.android.launcher3.testing.shared.TestProtocol.TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE;
 import static com.android.launcher3.util.TestConstants.AppNames.GMAIL_APP_NAME;
 import static com.android.launcher3.util.TestConstants.AppNames.MAPS_APP_NAME;
 import static com.android.launcher3.util.TestConstants.AppNames.PHOTOS_APP_NAME;
@@ -233,6 +234,11 @@
         final HomeAppIcon launcherTestAppIcon = createShortcutInCenterIfNotExist(TEST_APP_NAME);
         for (Point target : targets) {
             startTime = SystemClock.uptimeMillis();
+            Log.d(TEST_DRAG_APP_ICON_TO_MULTIPLE_WORKSPACES_FAILURE,
+                    "TaplDragTest.java.testDragAppIconToMultipleWorkspaceCells: shortcut name: "
+                            + launcherTestAppIcon.getIconName()
+                            + " | target cell coordinates: (" + target.x + ", " + target.y
+                            + ") | start time: " + startTime);
             launcherTestAppIcon.dragToWorkspace(target.x, target.y);
             endTime = SystemClock.uptimeMillis();
             elapsedTime = endTime - startTime;
diff --git a/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
index 34c41a6..2118ed6 100644
--- a/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
+++ b/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
@@ -30,6 +30,7 @@
 import java.util.concurrent.CountDownLatch
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -57,6 +58,8 @@
             TEST_ACTIVITY14
         )
 
+    @get:Rule(order = 0) val testStabilityRule = TestStabilityRule()
+
     @Before
     fun setUp() {
         modelHelper = LauncherModelHelper()
diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
index 802e564..a9947a0 100644
--- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
@@ -133,6 +133,7 @@
     }
 
     @Test
+    @com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord // b/325383911
     public void workTabExists() {
         assumeTrue(mWorkProfileSetupSuccessful);
         waitForWorkTabSetup();