Merge "Let the bubble bar handle gestures over the bubble bar." into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index b4132e4..9221f6b 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -27,3 +27,10 @@
     description: "Enables cursor hover states for certain elements."
     bug: "243191650"
 }
+
+flag {
+    name: "enable_responsive_workspace"
+    namespace: "launcher"
+    description: "Enables new workspace grid calculations method."
+    bug: "241386436"
+}
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index f82474a..1440498 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.anim.AlphaUpdateListener;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.keyboard.FocusIndicatorHelper;
 import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
 import com.android.launcher3.model.data.ItemInfo;
@@ -126,6 +127,10 @@
         int verticalPadding = getResources().getDimensionPixelSize(
                 R.dimen.all_apps_predicted_icon_vertical_padding);
         int totalHeight = iconHeight + iconPadding + textHeight + verticalPadding * 2;
+        if (FeatureFlags.enableTwolineAllapps()) {
+            // Add extra textHeight to the existing total height.
+            totalHeight += textHeight;
+        }
         return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
     }
 
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
new file mode 100644
index 0000000..6fe007c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.desktop
+
+import android.app.IApplicationThread
+import android.os.IBinder
+import android.os.RemoteException
+import android.util.Log
+import android.view.SurfaceControl
+import android.window.IRemoteTransition
+import android.window.IRemoteTransitionFinishedCallback
+import android.window.RemoteTransition
+import android.window.TransitionInfo
+import com.android.launcher3.statehandlers.DepthController
+import com.android.launcher3.statemanager.StateManager
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.quickstep.SystemUiProxy
+import com.android.quickstep.TaskViewUtils
+import com.android.quickstep.views.DesktopTaskView
+import java.util.function.Consumer
+
+/** Manage recents related operations with desktop tasks */
+class DesktopRecentsTransitionController(
+    private val stateManager: StateManager<*>,
+    private val systemUiProxy: SystemUiProxy,
+    private val appThread: IApplicationThread,
+    private val depthController: DepthController?
+) {
+
+    /** Launch desktop tasks from recents view */
+    fun launchDesktopFromRecents(
+        desktopTaskView: DesktopTaskView,
+        callback: Consumer<Boolean>? = null
+    ) {
+        val animRunner =
+            RemoteDesktopLaunchTransitionRunner(
+                desktopTaskView,
+                stateManager,
+                depthController,
+                callback
+            )
+        val transition = RemoteTransition(animRunner, appThread, "RecentsToDesktop")
+        systemUiProxy.showDesktopApps(desktopTaskView.display.displayId, transition)
+    }
+
+    private class RemoteDesktopLaunchTransitionRunner(
+        private val desktopTaskView: DesktopTaskView,
+        private val stateManager: StateManager<*>,
+        private val depthController: DepthController?,
+        private val successCallback: Consumer<Boolean>?
+    ) : IRemoteTransition.Stub() {
+
+        override fun startAnimation(
+            token: IBinder,
+            info: TransitionInfo,
+            t: SurfaceControl.Transaction,
+            finishCallback: IRemoteTransitionFinishedCallback
+        ) {
+            val errorHandlingFinishCallback = Runnable {
+                try {
+                    finishCallback.onTransitionFinished(null /* wct */, null /* sct */)
+                } catch (e: RemoteException) {
+                    Log.e(TAG, "Failed to call finish callback for desktop recents animation", e)
+                }
+            }
+
+            MAIN_EXECUTOR.execute {
+                TaskViewUtils.composeRecentsDesktopLaunchAnimator(
+                    desktopTaskView,
+                    stateManager,
+                    depthController,
+                    info,
+                    t
+                ) {
+                    errorHandlingFinishCallback.run()
+                    successCallback?.accept(true)
+                }
+            }
+        }
+
+        override fun mergeAnimation(
+            transition: IBinder,
+            info: TransitionInfo,
+            t: SurfaceControl.Transaction,
+            mergeTarget: IBinder,
+            finishCallback: IRemoteTransitionFinishedCallback
+        ) {}
+    }
+
+    companion object {
+        const val TAG = "DesktopRecentsTransitionController"
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index fa16b61..df6626a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -88,10 +88,10 @@
 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory;
 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter;
 import com.android.launcher3.util.DimensionUtils;
-import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.TouchController;
+import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.systemui.shared.rotation.FloatingRotationButton;
 import com.android.systemui.shared.rotation.RotationButton;
@@ -144,6 +144,7 @@
     private int mState;
 
     private final TaskbarActivityContext mContext;
+    private final WindowManagerProxy mWindowManagerProxy;
     private final FrameLayout mNavButtonsView;
     private final LinearLayout mNavButtonContainer;
     // Used for IME+A11Y buttons
@@ -198,10 +199,10 @@
             this::onComputeInsetsForSeparateWindow;
     private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
     private ImageView mRecentsButton;
-    private DisplayController mDisplayController;
 
     public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
         mContext = context;
+        mWindowManagerProxy = WindowManagerProxy.INSTANCE.get(mContext);
         mNavButtonsView = navButtonsView;
         mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
         mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
@@ -232,8 +233,6 @@
                         TaskbarManager.isPhoneMode(deviceProfile));
         mNavButtonsView.getLayoutParams().height = p.y;
 
-        mDisplayController = DisplayController.INSTANCE.get(mContext);
-
         mIsImeRenderingNavButtons =
                 InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
         if (!mIsImeRenderingNavButtons) {
@@ -738,7 +737,8 @@
             NavButtonLayoutter navButtonLayoutter =
                     NavButtonLayoutFactory.Companion.getUiLayoutter(
                             dp, mNavButtonsView, res, isInKidsMode, isInSetup, isThreeButtonNav,
-                            TaskbarManager.isPhoneMode(dp), mDisplayController.getInfo().rotation);
+                            TaskbarManager.isPhoneMode(dp),
+                            mWindowManagerProxy.getRotation(mContext));
             navButtonLayoutter.layoutButtons(dp, isContextualButtonShowing());
             updateNavButtonColor();
             return;
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 2acd5d4..13ffe6e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -57,14 +57,11 @@
             marginStart = 0
         }
 
-        // Swap recents and back button
-        navButtonContainer.addView(recentsButton)
-        navButtonContainer.addView(homeButton)
-        navButtonContainer.addView(backButton)
-
         navButtonContainer.layoutParams = navContainerParams
         navButtonContainer.gravity = Gravity.CENTER
 
+        addThreeButtons()
+
         // Add the spaces in between the nav buttons
         val spaceInBetween: Int =
             resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
@@ -85,4 +82,11 @@
             }
         }
     }
+
+    open fun addThreeButtons() {
+        // Swap recents and back button
+        navButtonContainer.addView(recentsButton)
+        navButtonContainer.addView(homeButton)
+        navButtonContainer.addView(backButton)
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
index f0fe581..2d62c3f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
@@ -19,7 +19,6 @@
 import android.content.res.Resources
 import android.view.ViewGroup
 import android.widget.LinearLayout
-import com.android.launcher3.DeviceProfile
 
 class PhoneSeascapeNavLayoutter(
         resources: Resources,
@@ -34,10 +33,7 @@
                 startContextualContainer
         ) {
 
-    override fun layoutButtons(dp: DeviceProfile, isContextualButtonShowing: Boolean) {
-        // TODO(b/230395757): Polish pending, this is just to make it usable
-        super.layoutButtons(dp, isContextualButtonShowing)
-        navButtonContainer.removeAllViews()
+    override fun addThreeButtons() {
         // Flip ordering of back and recents buttons
         navButtonContainer.addView(backButton)
         navButtonContainer.addView(homeButton)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 40909d5..1b22c84 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -106,6 +106,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.appprediction.PredictionRowView;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.desktop.DesktopRecentsTransitionController;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
@@ -225,6 +226,9 @@
      */
     private PendingSplitSelectInfo mPendingSplitSelectInfo = null;
 
+    @Nullable
+    private DesktopRecentsTransitionController mDesktopRecentsTransitionController;
+
     private SafeCloseable mViewCapture;
 
     private boolean mEnableWidgetDepth;
@@ -235,12 +239,19 @@
 
         mActionsView = findViewById(R.id.overview_actions_view);
         RecentsView overviewPanel = getOverviewPanel();
+        SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
         mSplitSelectStateController =
                 new SplitSelectStateController(this, mHandler, getStateManager(),
                         getDepthController(), getStatsLogManager(),
-                        SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this),
+                        systemUiProxy, RecentsModel.INSTANCE.get(this),
                         () -> onStateBack());
-        overviewPanel.init(mActionsView, mSplitSelectStateController);
+        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+            mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
+                    getStateManager(), systemUiProxy, getIApplicationThread(),
+                    getDepthController());
+        }
+        overviewPanel.init(mActionsView, mSplitSelectStateController,
+                mDesktopRecentsTransitionController);
         mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
                 mSplitSelectStateController);
         mSplitToWorkspaceController = new SplitToWorkspaceController(this,
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index d66421f..3c7fbf0 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -60,6 +60,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.desktop.DesktopRecentsTransitionController;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
@@ -82,6 +83,7 @@
 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.TISBindHelper;
+import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -121,6 +123,8 @@
     private final Handler mHandler = new Handler();
     private final Runnable mAnimationStartTimeoutRunnable = this::onAnimationStartTimeout;
     private SplitSelectStateController mSplitSelectStateController;
+    @Nullable
+    private DesktopRecentsTransitionController mDesktopRecentsTransitionController;
 
     /**
      * Init drag layer and overview panel views.
@@ -133,13 +137,21 @@
         mFallbackRecentsView = findViewById(R.id.overview_panel);
         mActionsView = findViewById(R.id.overview_actions_view);
         getRootView().getSysUiScrim().getSysUIProgress().updateValue(0);
+        SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
         mSplitSelectStateController =
                 new SplitSelectStateController(this, mHandler, getStateManager(),
                         null /* depthController */, getStatsLogManager(),
-                        SystemUiProxy.INSTANCE.get(this), RecentsModel.INSTANCE.get(this),
+                        systemUiProxy, RecentsModel.INSTANCE.get(this),
                         null /*activityBackCallback*/);
         mDragLayer.recreateControllers();
-        mFallbackRecentsView.init(mActionsView, mSplitSelectStateController);
+        if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+            mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
+                    getStateManager(), systemUiProxy, getIApplicationThread(),
+                    null /* depthController */
+            );
+        }
+        mFallbackRecentsView.init(mActionsView, mSplitSelectStateController,
+                mDesktopRecentsTransitionController);
 
         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
     }
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 170847d..3429df1 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -1268,10 +1268,10 @@
     //
 
     /** Call shell to show all apps active on the desktop */
-    public void showDesktopApps(int displayId) {
+    public void showDesktopApps(int displayId, @Nullable RemoteTransition transition) {
         if (mDesktopMode != null) {
             try {
-                mDesktopMode.showDesktopApps(displayId);
+                mDesktopMode.showDesktopApps(displayId, transition);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call showDesktopApps", e);
             }
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 34965df..819f249 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -613,6 +613,42 @@
         animator.start();
     }
 
+    /**
+     * Start recents to desktop animation
+     */
+    public static void composeRecentsDesktopLaunchAnimator(
+            @NonNull DesktopTaskView launchingTaskView,
+            @NonNull StateManager stateManager, @Nullable DepthController depthController,
+            @NonNull TransitionInfo transitionInfo,
+            SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
+
+        AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                t.apply();
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finishCallback.run();
+            }
+        });
+
+        final RemoteAnimationTarget[] apps = RemoteAnimationTargetCompat.wrapApps(
+                transitionInfo, t, null /* leashMap */);
+        final RemoteAnimationTarget[] wallpaper = RemoteAnimationTargetCompat.wrapNonApps(
+                transitionInfo, true /* wallpapers */, t, null /* leashMap */);
+        final RemoteAnimationTarget[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(
+                transitionInfo, false /* wallpapers */, t, null /* leashMap */);
+
+        composeRecentsLaunchAnimator(animatorSet, launchingTaskView, apps, wallpaper, nonApps,
+                true /* launcherClosing */, stateManager, launchingTaskView.getRecentsView(),
+                depthController);
+
+        animatorSet.start();
+    }
+
     public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
             @NonNull RemoteAnimationTarget[] appTargets,
             @NonNull RemoteAnimationTarget[] wallpaperTargets,
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index d2c6ad2..02d0f39 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -923,8 +923,8 @@
             handleOrientationSetup(base);
         }
         if (mDeviceState.isFullyGesturalNavMode() || newGestureState.isTrackpadGesture()) {
-            String reasonPrefix = "device is in gesture navigation mode or 3-button mode with a"
-                    + " trackpad gesture";
+            String reasonPrefix =
+                    "device is in gesture navigation mode or 3-button mode with a trackpad gesture";
             if (mDeviceState.canTriggerAssistantAction(event)) {
                 reasonString.append(NEWLINE_PREFIX)
                         .append(reasonPrefix)
@@ -945,8 +945,8 @@
                     reasonString.append(NEWLINE_PREFIX)
                             .append(reasonPrefix)
                             .append(SUBSTRING_PREFIX)
-                            .append("TaskbarActivityContext != null, "
-                                    + "using TaskbarUnstashInputConsumer");
+                            .append("TaskbarActivityContext != null, ")
+                            .append("using TaskbarUnstashInputConsumer");
                     base = new TaskbarUnstashInputConsumer(this, base, mInputMonitorCompat, tac,
                             mOverviewCommandHelper);
                 }
@@ -955,8 +955,8 @@
                 reasonString.append(NEWLINE_PREFIX)
                         .append(reasonPrefix)
                         .append(SUBSTRING_PREFIX)
-                        .append("Long press nav handle enabled, "
-                                + "using NavHandleLongPressInputConsumer");
+                        .append("Long press nav handle enabled, ")
+                        .append("using NavHandleLongPressInputConsumer");
                 base = new NavHandleLongPressInputConsumer(this, base, mInputMonitorCompat);
             }
 
@@ -1149,8 +1149,8 @@
         if ((mDeviceState.isFullyGesturalNavMode() || gestureState.isTrackpadGesture())
                 && gestureState.getRunningTask() != null) {
             reasonString.append(SUBSTRING_PREFIX)
-                    .append("device is in gesture nav mode or 3-button mode with a trackpad gesture"
-                            + "and running task != null")
+                    .append("device is in gesture nav mode or 3-button mode with a trackpad")
+                    .append(" gesture and running task != null")
                     .append(", using DeviceLockedInputConsumer");
             return new DeviceLockedInputConsumer(
                     this, mDeviceState, mTaskAnimationManager, gestureState, mInputMonitorCompat);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 95d88cd..059b0ce 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.desktop.DesktopRecentsTransitionController;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.SplitConfigurationOptions;
@@ -73,8 +74,9 @@
     }
 
     @Override
-    public void init(OverviewActionsView actionsView, SplitSelectStateController splitController) {
-        super.init(actionsView, splitController);
+    public void init(OverviewActionsView actionsView, SplitSelectStateController splitController,
+            @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
+        super.init(actionsView, splitController, desktopRecentsTransitionController);
         setOverviewStateEnabled(true);
         setOverlayEnabled(true);
     }
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 5135345..32d6582 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -44,10 +44,10 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.desktop.DesktopRecentsTransitionController;
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.util.RunnableList;
 import com.android.quickstep.RecentsModel;
-import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.RecentsOrientedState;
@@ -331,17 +331,21 @@
         return false;
     }
 
-    @Override
-    public RunnableList launchTasks() {
-        SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps(mActivity.getDisplayId());
-        Launcher.getLauncher(mActivity).getStateManager().goToState(NORMAL, false /* animated */);
-        return null;
-    }
-
     @Nullable
     @Override
     public RunnableList launchTaskAnimated() {
-        return launchTasks();
+        RunnableList endCallback = new RunnableList();
+        endCallback.add(() -> Launcher.getLauncher(mActivity).getStateManager().goToState(NORMAL));
+
+        DesktopRecentsTransitionController recentsController =
+                getRecentsView().getDesktopRecentsController();
+        if (recentsController != null) {
+            recentsController.launchDesktopFromRecents(this, success -> {
+                endCallback.executeAllAndDestroy();
+            });
+        }
+
+        return endCallback;
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 892ebf9..1867fe9 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.desktop.DesktopRecentsTransitionController;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
@@ -75,8 +76,9 @@
 
     @Override
     public void init(OverviewActionsView actionsView,
-            SplitSelectStateController splitPlaceholderView) {
-        super.init(actionsView, splitPlaceholderView);
+            SplitSelectStateController splitPlaceholderView,
+            @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
+        super.init(actionsView, splitPlaceholderView, desktopRecentsTransitionController);
         setContentAlpha(0);
     }
 
@@ -276,7 +278,8 @@
             desktopVisibilityController.setRecentsGestureEnd(endTarget);
         }
         if (showDesktopApps) {
-            SystemUiProxy.INSTANCE.get(mActivity).showDesktopApps(mActivity.getDisplayId());
+            SystemUiProxy.INSTANCE.get(mActivity).showDesktopApps(mActivity.getDisplayId(),
+                    null /* transition */);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 9b41ccd..8888c0d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -143,6 +143,7 @@
 import com.android.launcher3.anim.SpringProperty;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.desktop.DesktopRecentsTransitionController;
 import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.statehandlers.DepthController;
@@ -722,6 +723,9 @@
     private ObjectAnimator mActionsViewAlphaAnimator;
     private float mActionsViewAlphaAnimatorFinalValue;
 
+    @Nullable
+    private DesktopRecentsTransitionController mDesktopRecentsTransitionController;
+
     /**
      * Keeps track of the desktop task. Optional and only present when the feature flag is enabled.
      */
@@ -1042,10 +1046,12 @@
         updateTaskStackListenerState();
     }
 
-    public void init(OverviewActionsView actionsView, SplitSelectStateController splitController) {
+    public void init(OverviewActionsView actionsView, SplitSelectStateController splitController,
+            @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
         mActionsView = actionsView;
         mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
         mSplitSelectStateController = splitController;
+        mDesktopRecentsTransitionController = desktopRecentsTransitionController;
     }
 
     public SplitSelectStateController getSplitSelectController() {
@@ -5787,6 +5793,11 @@
         return null;
     }
 
+    @Nullable
+    protected DesktopRecentsTransitionController getDesktopRecentsController() {
+        return mDesktopRecentsTransitionController;
+    }
+
     /** Enables or disables modal state for RecentsView */
     public abstract void setModalStateEnabled(int taskId, boolean animate);
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index f84b549..d5b43a8 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -107,7 +107,6 @@
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.TaskCornerRadius;
 import com.android.quickstep.util.TaskRemovedDuringLaunchListener;
-import com.android.quickstep.util.TransformParams;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -975,19 +974,17 @@
             if (remoteTargetHandles.length == 1) {
                 targets = remoteTargetHandles[0].getTransformParams().getTargetSet();
             } else {
-                TransformParams topLeftParams = remoteTargetHandles[0].getTransformParams();
-                TransformParams rightBottomParams = remoteTargetHandles[1].getTransformParams();
-                RemoteAnimationTarget[] apps = Stream.concat(
-                        Arrays.stream(topLeftParams.getTargetSet().apps),
-                        Arrays.stream(rightBottomParams.getTargetSet().apps))
+                RemoteAnimationTarget[] apps = Arrays.stream(remoteTargetHandles)
+                        .flatMap(handle -> Stream.of(
+                                handle.getTransformParams().getTargetSet().apps))
                         .toArray(RemoteAnimationTarget[]::new);
-                RemoteAnimationTarget[] wallpapers = Stream.concat(
-                        Arrays.stream(topLeftParams.getTargetSet().wallpapers),
-                        Arrays.stream(rightBottomParams.getTargetSet().wallpapers))
+                RemoteAnimationTarget[] wallpapers = Arrays.stream(remoteTargetHandles)
+                        .flatMap(handle -> Stream.of(
+                                handle.getTransformParams().getTargetSet().wallpapers))
                         .toArray(RemoteAnimationTarget[]::new);
                 targets = new RemoteAnimationTargets(apps, wallpapers,
-                        topLeftParams.getTargetSet().nonApps,
-                        topLeftParams.getTargetSet().targetMode);
+                        remoteTargetHandles[0].getTransformParams().getTargetSet().nonApps,
+                        remoteTargetHandles[0].getTransformParams().getTargetSet().targetMode);
             }
             if (targets == null) {
                 // If the recents animation is cancelled somehow between the parent if block and
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index bd004e9..ab9836f 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V2;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_ICON_LABEL_AUTO_SCALING;
 import static com.android.launcher3.config.FeatureFlags.enableCursorHoverStates;
@@ -39,6 +40,7 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.icu.text.MessageFormat;
+import android.text.StaticLayout;
 import android.text.TextPaint;
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
@@ -148,6 +150,7 @@
     private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
     private final ActivityContext mActivity;
     private FastBitmapDrawable mIcon;
+    private DeviceProfile mDeviceProfile;
     private boolean mCenterVertically;
 
     protected int mDisplay;
@@ -206,35 +209,35 @@
         mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
         mIsRtl = (getResources().getConfiguration().getLayoutDirection()
                 == View.LAYOUT_DIRECTION_RTL);
-        DeviceProfile grid = mActivity.getDeviceProfile();
+        mDeviceProfile = mActivity.getDeviceProfile();
 
         mDisplay = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
         final int defaultIconSize;
         if (mDisplay == DISPLAY_WORKSPACE) {
-            setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
-            setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
-            defaultIconSize = grid.iconSizePx;
-            setCenterVertically(grid.iconCenterVertically);
+            setTextSize(TypedValue.COMPLEX_UNIT_PX, mDeviceProfile.iconTextSizePx);
+            setCompoundDrawablePadding(mDeviceProfile.iconDrawablePaddingPx);
+            defaultIconSize = mDeviceProfile.iconSizePx;
+            setCenterVertically(mDeviceProfile.iconCenterVertically);
         } else if (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW
                 || mDisplay == DISPLAY_SEARCH_RESULT_APP_ROW) {
-            setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
-            setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
-            defaultIconSize = grid.allAppsIconSizePx;
+            setTextSize(TypedValue.COMPLEX_UNIT_PX, mDeviceProfile.allAppsIconTextSizePx);
+            setCompoundDrawablePadding(mDeviceProfile.allAppsIconDrawablePaddingPx);
+            defaultIconSize = mDeviceProfile.allAppsIconSizePx;
         } else if (mDisplay == DISPLAY_FOLDER) {
-            setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
-            setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
-            defaultIconSize = grid.folderChildIconSizePx;
+            setTextSize(TypedValue.COMPLEX_UNIT_PX, mDeviceProfile.folderChildTextSizePx);
+            setCompoundDrawablePadding(mDeviceProfile.folderChildDrawablePaddingPx);
+            defaultIconSize = mDeviceProfile.folderChildIconSizePx;
         } else if (mDisplay == DISPLAY_SEARCH_RESULT) {
-            setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
+            setTextSize(TypedValue.COMPLEX_UNIT_PX, mDeviceProfile.allAppsIconTextSizePx);
             defaultIconSize = getResources().getDimensionPixelSize(R.dimen.search_row_icon_size);
         } else if (mDisplay == DISPLAY_SEARCH_RESULT_SMALL) {
             defaultIconSize = getResources().getDimensionPixelSize(
                     R.dimen.search_row_small_icon_size);
         } else if (mDisplay == DISPLAY_TASKBAR) {
-            defaultIconSize = grid.iconSizePx;
+            defaultIconSize = mDeviceProfile.iconSizePx;
         } else {
             // widget_selection or shortcut_popup
-            defaultIconSize = grid.iconSizePx;
+            defaultIconSize = mDeviceProfile.iconSizePx;
         }
 
         mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
@@ -274,8 +277,7 @@
         mDotParams.scale = 0f;
         mForceHideDot = false;
         setBackground(null);
-        if (Flags.enableTwolineAllapps() || FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get()
-                || FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()) {
+        if (FeatureFlags.enableTwolineAllapps() || FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()) {
             setMaxLines(1);
         }
 
@@ -407,8 +409,8 @@
      *  Only if actual text can be displayed in two line, the {@code true} value will be effective.
      */
     protected boolean shouldUseTwoLine() {
-        return ((Flags.enableTwolineAllapps() || FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get())
-                && mDisplay == DISPLAY_ALL_APPS)
+        return ((FeatureFlags.enableTwolineAllapps())
+                && (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW))
                 || (FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()
                 && mDisplay == DISPLAY_SEARCH_RESULT);
     }
@@ -691,21 +693,28 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int height = MeasureSpec.getSize(heightMeasureSpec);
         if (mCenterVertically) {
             Paint.FontMetrics fm = getPaint().getFontMetrics();
             int cellHeightPx = mIconSize + getCompoundDrawablePadding() +
                     (int) Math.ceil(fm.bottom - fm.top);
-            int height = MeasureSpec.getSize(heightMeasureSpec);
             setPadding(getPaddingLeft(), (height - cellHeightPx) / 2, getPaddingRight(),
                     getPaddingBottom());
         }
         // Only apply two line for all_apps and device search only if necessary.
         if (shouldUseTwoLine() && (mLastOriginalText != null)) {
+            int allowedVerticalSpace = height - getPaddingTop() - getPaddingBottom()
+                    - mDeviceProfile.allAppsIconSizePx
+                    - mDeviceProfile.allAppsIconDrawablePaddingPx;
             CharSequence modifiedString = modifyTitleToSupportMultiLine(
                     MeasureSpec.getSize(widthMeasureSpec) - getCompoundPaddingLeft()
                             - getCompoundPaddingRight(),
+                    allowedVerticalSpace,
                     mLastOriginalText,
-                    getPaint(), mBreakPointsIntArray);
+                    getPaint(),
+                    mBreakPointsIntArray,
+                    getLineSpacingMultiplier(),
+                    getLineSpacingExtra());
             if (!TextUtils.equals(modifiedString, mLastModifiedText)) {
                 mLastModifiedText = modifiedString;
                 setText(modifiedString);
@@ -785,7 +794,8 @@
      * many words as it can until the limit is reached. Once the limit is reached, we decide to
      * either return the original title or continue on a new line. How to get the new string is by
      * iterating through the list of break points and determining if the strings between the break
-     * points can fit within the line it is in.
+     * points can fit within the line it is in. We will show the modified string if there is enough
+     * horizontal and vertical space, otherwise this method will just return the original string.
      *  Example assuming each character takes up one spot:
      *  title = "Battery Stats", breakpoint = [6], stringPtr = 0, limitedWidth = 7
      *  We get the current word -> from sublist(0, breakpoint[i]+1) so sublist (0,7) -> Battery,
@@ -794,8 +804,9 @@
      *  if the first char is a SPACE, we trim to append "Stats". So resulting string would be
      *  "Battery\nStats"
      */
-    public static CharSequence modifyTitleToSupportMultiLine(int limitedWidth, CharSequence title,
-            TextPaint paint, IntArray breakPoints) {
+    public static CharSequence modifyTitleToSupportMultiLine(int limitedWidth, int limitedHeight,
+            CharSequence title, TextPaint paint, IntArray breakPoints, float spacingMultiplier,
+            float spacingExtra) {
         // current title is less than the width allowed so we can just skip
         if (title == null || paint.measureText(title, 0, title.length()) <= limitedWidth) {
             return title;
@@ -816,11 +827,7 @@
             if (runningWidth <= limitedWidth) {
                 newString.append(currentWord);
             } else {
-                // there is no more space
-                if (i == 0) {
-                    // if the first words exceeds width, just return as the first line will ellipse
-                    return title;
-                } else {
+                if (i != 0)  {
                     // If putting word onto a new line, make sure there is no space or new line
                     // character in the beginning of the current word and just put in the rest of
                     // the characters.
@@ -834,8 +841,14 @@
                                 : EMPTY;
                     }
                     newString.append(NEW_LINE).append(lastCharacters);
-                    return newString.toString();
+                    StaticLayout staticLayout = new StaticLayout(newString, paint, limitedWidth,
+                            ALIGN_NORMAL, spacingMultiplier, spacingExtra, false);
+                    if (staticLayout.getHeight() < limitedHeight) {
+                        return newString.toString();
+                    }
                 }
+                // if the first words exceeds width, just return as the first line will ellipse
+                return title;
             }
             if (i >= breakPoints.size()) {
                 // no need to look forward into the string if we've already finished processing
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index c05afc9..bf35a0f 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -1133,6 +1133,10 @@
         if (isVerticalBarLayout()) {
             hideWorkspaceLabelsIfNotEnoughSpace();
         }
+        if (FeatureFlags.enableTwolineAllapps()) {
+            // Add extra textHeight to the existing allAppsCellHeight.
+            allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx);
+        }
 
         updateHotseatSizes(iconSizePx);
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index c619bc5..8707aba 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -888,7 +888,7 @@
             deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory,
                     DEVICE_CATEGORY_ALL);
 
-            if (FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE.get()) {
+            if (FeatureFlags.enableResponsiveWorkspace()) {
                 mWorkspaceSpecsId = a.getResourceId(
                         R.styleable.GridDisplayOption_workspaceSpecsId, INVALID_RESOURCE_HANDLE);
                 mWorkspaceSpecsTwoPanelId = a.getResourceId(
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 769c787..005e6df 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -29,7 +29,6 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Flags;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.search.SearchAdapterProvider;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
@@ -138,7 +137,6 @@
     protected final OnClickListener mOnIconClickListener;
     protected final OnLongClickListener mOnIconLongClickListener;
     protected OnFocusChangeListener mIconFocusListener;
-    private final int mExtraTextHeight;
 
     public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater,
             AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider) {
@@ -150,8 +148,6 @@
         mOnIconLongClickListener = mActivityContext.getAllAppsItemLongClickListener();
 
         mAdapterProvider = adapterProvider;
-        mExtraTextHeight = Utilities.calculateTextHeight(
-                mActivityContext.getDeviceProfile().allAppsIconTextSizePx);
     }
 
     /** Checks if the passed viewType represents all apps divider. */
@@ -177,9 +173,7 @@
     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         switch (viewType) {
             case VIEW_TYPE_ICON:
-                int layout =
-                        !(Flags.enableTwolineAllapps() || FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get())
-                                ? R.layout.all_apps_icon
+                int layout = !FeatureFlags.enableTwolineAllapps() ? R.layout.all_apps_icon
                                 : R.layout.all_apps_icon_twoline;
                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                         layout, parent, false);
@@ -190,9 +184,6 @@
                 // Ensure the all apps icon height matches the workspace icons in portrait mode.
                 icon.getLayoutParams().height =
                         mActivityContext.getDeviceProfile().allAppsCellHeightPx;
-                if (Flags.enableTwolineAllapps() || FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get()) {
-                    icon.getLayoutParams().height += mExtraTextHeight;
-                }
                 return new ViewHolder(icon);
             case VIEW_TYPE_EMPTY_SEARCH:
                 return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 2da85e1..ae0d9f0 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -243,6 +243,9 @@
     // Aconfig migration complete for ENABLE_TWOLINE_ALLAPPS.
     public static final BooleanFlag ENABLE_TWOLINE_ALLAPPS = getDebugFlag(270390937,
             "ENABLE_TWOLINE_ALLAPPS", DISABLED, "Enables two line label inside all apps.");
+    public static boolean enableTwolineAllapps() {
+        return ENABLE_TWOLINE_ALLAPPS.get() || Flags.enableTwolineAllapps();
+    }
 
     public static final BooleanFlag IME_STICKY_SNACKBAR_EDU = getDebugFlag(270391693,
             "IME_STICKY_SNACKBAR_EDU", ENABLED, "Show sticky IME edu in AllApps");
@@ -422,9 +425,14 @@
     // TODO(Block 31): Clean up flags
 
     // TODO(Block 32): Clean up flags
+    // Aconfig migration complete for ENABLE_RESPONSIVE_WORKSPACE.
+    @VisibleForTesting
     public static final BooleanFlag ENABLE_RESPONSIVE_WORKSPACE = getDebugFlag(241386436,
-            "ENABLE_RESPONSIVE_WORKSPACE", DISABLED,
+            "ENABLE_RESPONSIVE_WORKSPACE", TEAMFOOD,
             "Enables new workspace grid calculations method.");
+    public static boolean enableResponsiveWorkspace() {
+        return ENABLE_RESPONSIVE_WORKSPACE.get() || Flags.enableResponsiveWorkspace();
+    }
 
     // TODO(Block 33): Clean up flags
     public static final BooleanFlag ENABLE_ALL_APPS_RV_PREINFLATION = getDebugFlag(288161355,
diff --git a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
index bf7a21c..fb364ad 100644
--- a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
+++ b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -29,13 +29,13 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.ModelDbController;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.ModelTestExtensions;
 
 import java.util.ArrayList;
 import java.util.function.Supplier;
@@ -60,8 +60,7 @@
     public void commit() {
         LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
         // Load the model once so that there is no pending migration:
-        loadModelSync(model);
-
+        ModelTestExtensions.INSTANCE.loadModelSync(model);
         runOnExecutorSync(MODEL_EXECUTOR, () -> {
             ModelDbController controller = model.getModelDbController();
             // Migrate any previous data so that the DB state is correct
@@ -105,16 +104,7 @@
 
         // Reload model
         runOnExecutorSync(MAIN_EXECUTOR, model::forceReload);
-        loadModelSync(model);
-    }
-
-    private void loadModelSync(LauncherModel model) {
-        Callbacks mockCb = new Callbacks() { };
-        runOnExecutorSync(MAIN_EXECUTOR, () -> model.addCallbacksAndLoad(mockCb));
-        runOnExecutorSync(MODEL_EXECUTOR, () -> { });
-
-        runOnExecutorSync(MAIN_EXECUTOR, () -> { });
-        runOnExecutorSync(MAIN_EXECUTOR, () -> model.removeCallbacks(mockCb));
+        ModelTestExtensions.INSTANCE.loadModelSync(model);
     }
 
     /**
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
index 00d7ce6..3d388d60 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.celllayout;
 
+import static android.platform.uiautomator_helpers.DeviceHelpers.getContext;
+
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -28,13 +30,16 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.MultipageCellLayout;
 import com.android.launcher3.tapl.Widget;
 import com.android.launcher3.tapl.WidgetResizeFrame;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.ModelTestExtensions;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Before;
@@ -68,6 +73,13 @@
         TaplTestsLauncher3.initialize(this);
     }
 
+    @After
+    public void tearDown() {
+        ModelTestExtensions.INSTANCE.clearModelDb(
+                LauncherAppState.getInstance(getContext()).getModel()
+        );
+    }
+
     /**
      * Validate if the given board represent the current CellLayout
      **/
diff --git a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index bba8c89..e6fdbaa 100644
--- a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -20,13 +20,13 @@
 
 import static com.android.launcher3.BubbleTextView.DISPLAY_ALL_APPS;
 import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TWOLINE_ALLAPPS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
 import android.content.Context;
 import android.graphics.Typeface;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.ViewGroup;
 
 import com.android.launcher3.BubbleTextView;
@@ -36,14 +36,11 @@
 import com.android.launcher3.search.StringMatcherUtility;
 import com.android.launcher3.util.ActivityContextWrapper;
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.TestUtil;
-import com.android.launcher3.util.rule.StaticMockitoRule;
 import com.android.launcher3.views.BaseDragLayer;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 /**
@@ -55,7 +52,7 @@
  */
 public class BubbleTextViewTest {
 
-    @Rule public StaticMockitoRule mockitoRule = new StaticMockitoRule(Flags.class);
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private static final StringMatcherUtility.StringMatcher
             MATCHER = StringMatcherUtility.StringMatcher.getInstance();
     private static final int ONE_LINE = 1;
@@ -75,6 +72,10 @@
             "LEGO®\nBuilder";
     private static final String EMPTY_STRING = "";
     private static final int CHAR_CNT = 7;
+    private static final int MAX_HEIGHT = Integer.MAX_VALUE;
+    private static final int LIMITED_HEIGHT = 357; /* allowedHeight in Pixel6 */
+    private static final float SPACE_MULTIPLIER = 1;
+    private static final float SPACE_EXTRA = 0;
 
     private BubbleTextView mBubbleTextView;
     private ItemInfoWithIcon mItemInfoWithIcon;
@@ -84,7 +85,8 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        Mockito.when(Flags.enableTwolineAllapps()).thenReturn(false);
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_CURSOR_HOVER_STATES);
         Utilities.enableRunningInTestHarnessForTests();
         mContext = new ActivityContextWrapper(getApplicationContext());
         mBubbleTextView = new BubbleTextView(mContext);
@@ -111,160 +113,150 @@
 
     @Test
     public void testEmptyString_flagOn() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
-            mItemInfoWithIcon.title = EMPTY_STRING;
-            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
-            mBubbleTextView.applyLabel(mItemInfoWithIcon);
-            mBubbleTextView.setTypeface(Typeface.MONOSPACE);
-            mBubbleTextView.measure(mLimitedWidth, 0);
-            mBubbleTextView.onPreDraw();
-            assertNotEquals(TWO_LINE, mBubbleTextView.getMaxLines());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        mItemInfoWithIcon.title = EMPTY_STRING;
+        mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+        mBubbleTextView.onPreDraw();
+
+        assertNotEquals(TWO_LINE, mBubbleTextView.getMaxLines());
     }
 
     @Test
     public void testEmptyString_flagOff() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
-            mItemInfoWithIcon.title = EMPTY_STRING;
-            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
-            mBubbleTextView.applyLabel(mItemInfoWithIcon);
-            mBubbleTextView.setTypeface(Typeface.MONOSPACE);
-            mBubbleTextView.measure(mLimitedWidth, 0);
-            mBubbleTextView.onPreDraw();
-            assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        mItemInfoWithIcon.title = EMPTY_STRING;
+        mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+        mBubbleTextView.onPreDraw();
+
+        assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
     }
 
     @Test
     public void testStringWithSpaceLongerThanCharLimit_flagOn() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
-            // test string: "Battery Stats"
-            mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
-            mBubbleTextView.applyLabel(mItemInfoWithIcon);
-            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
-            mBubbleTextView.setTypeface(Typeface.MONOSPACE);
-            mBubbleTextView.measure(mLimitedWidth, 0);
-            mBubbleTextView.onPreDraw();
-            assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        // test string: "Battery Stats"
+        mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+
+        mBubbleTextView.onPreDraw();
+
+        assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
     }
 
     @Test
     public void testStringWithSpaceLongerThanCharLimit_flagOff() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
-            // test string: "Battery Stats"
-            mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
-            mBubbleTextView.applyLabel(mItemInfoWithIcon);
-            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
-            mBubbleTextView.setTypeface(Typeface.MONOSPACE);
-            mBubbleTextView.measure(mLimitedWidth, 0);
-            mBubbleTextView.onPreDraw();
-            assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        // test string: "Battery Stats"
+        mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+        mBubbleTextView.onPreDraw();
+
+        assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
     }
 
     @Test
     public void testLongStringNoSpaceLongerThanCharLimit_flagOn() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
-            // test string: "flutterappflorafy"
-            mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
-            mBubbleTextView.applyLabel(mItemInfoWithIcon);
-            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
-            mBubbleTextView.setTypeface(Typeface.MONOSPACE);
-            mBubbleTextView.measure(mLimitedWidth, 0);
-            mBubbleTextView.onPreDraw();
-            assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        // test string: "flutterappflorafy"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+        mBubbleTextView.onPreDraw();
+
+        assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
     }
 
     @Test
     public void testLongStringNoSpaceLongerThanCharLimit_flagOff() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
-            // test string: "flutterappflorafy"
-            mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
-            mBubbleTextView.applyLabel(mItemInfoWithIcon);
-            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
-            mBubbleTextView.setTypeface(Typeface.MONOSPACE);
-            mBubbleTextView.measure(mLimitedWidth, 0);
-            mBubbleTextView.onPreDraw();
-            assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
-        } catch (Exception e) {
-            throw  new RuntimeException(e);
-        }
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        // test string: "flutterappflorafy"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+        mBubbleTextView.onPreDraw();
+
+        assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
     }
 
     @Test
     public void testLongStringWithSpaceLongerThanCharLimit_flagOn() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
-            // test string: "System UWB Field Test"
-            mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
-            mBubbleTextView.applyLabel(mItemInfoWithIcon);
-            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
-            mBubbleTextView.setTypeface(Typeface.MONOSPACE);
-            mBubbleTextView.measure(mLimitedWidth, 0);
-            mBubbleTextView.onPreDraw();
-            assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
-        } catch (Exception e) {
-            throw  new RuntimeException(e);
-        }
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        // test string: "System UWB Field Test"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+
+        mBubbleTextView.onPreDraw();
+
+        assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
     }
 
     @Test
     public void testLongStringWithSpaceLongerThanCharLimit_flagOff() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
-            // test string: "System UWB Field Test"
-            mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
-            mBubbleTextView.applyLabel(mItemInfoWithIcon);
-            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
-            mBubbleTextView.setTypeface(Typeface.MONOSPACE);
-            mBubbleTextView.measure(mLimitedWidth, 0);
-            mBubbleTextView.onPreDraw();
-            assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
-        } catch (Exception e) {
-            throw  new RuntimeException(e);
-        }
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        // test string: "System UWB Field Test"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+        mBubbleTextView.onPreDraw();
+
+        assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
     }
 
     @Test
     public void testLongStringSymbolLongerThanCharLimit_flagOn() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
-            // test string: "LEGO®Builder"
-            mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
-            mBubbleTextView.applyLabel(mItemInfoWithIcon);
-            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
-            mBubbleTextView.setTypeface(Typeface.MONOSPACE);
-            mBubbleTextView.measure(mLimitedWidth, 0);
-            mBubbleTextView.onPreDraw();
-            assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
-        } catch (Exception e) {
-            throw  new RuntimeException(e);
-        }
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        // test string: "LEGO®Builder"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+
+        mBubbleTextView.onPreDraw();
+
+        assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
     }
 
     @Test
     public void testLongStringSymbolLongerThanCharLimit_flagOff() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, false)) {
-            // test string: "LEGO®Builder"
-            mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
-            mBubbleTextView.applyLabel(mItemInfoWithIcon);
-            mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
-            mBubbleTextView.setTypeface(Typeface.MONOSPACE);
-            mBubbleTextView.measure(mLimitedWidth, 0);
-            mBubbleTextView.onPreDraw();
-            assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
-        } catch (Exception e) {
-            throw  new RuntimeException(e);
-        }
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        // test string: "LEGO®Builder"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+        mBubbleTextView.onPreDraw();
+
+        assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
     }
 
     @Test
@@ -273,8 +265,11 @@
         IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
                 TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
         CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
+                MAX_HEIGHT,
                 TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
-                breakPoints);
+                breakPoints,
+                SPACE_MULTIPLIER,
+                SPACE_EXTRA);
         assertEquals(TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
     }
 
@@ -283,9 +278,11 @@
         // test string: "flutterappflorafy"
         IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
                 TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
-        CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
+        CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth, 0,
                 TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
-                breakPoints);
+                breakPoints,
+                SPACE_MULTIPLIER,
+                SPACE_EXTRA);
         assertEquals(TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT, newString);
     }
 
@@ -295,8 +292,11 @@
         IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
                 TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, MATCHER);
         CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
+                MAX_HEIGHT,
                 TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
-                breakPoints);
+                breakPoints,
+                SPACE_MULTIPLIER,
+                SPACE_EXTRA);
         assertEquals(TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
     }
 
@@ -305,25 +305,57 @@
         // test string: "LEGO®Builder"
         IntArray breakPoints = StringMatcherUtility.getListOfBreakpoints(
                 TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT, MATCHER);
-        CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(mLimitedWidth,
+        CharSequence newString = BubbleTextView.modifyTitleToSupportMultiLine(
+                mLimitedWidth,
+                MAX_HEIGHT,
                 TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT, mBubbleTextView.getPaint(),
-                breakPoints);
+                breakPoints,
+                SPACE_MULTIPLIER,
+                SPACE_EXTRA);
         assertEquals(TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT_RESULT, newString);
     }
 
     @Test
-    public void testEnsurePredictionRowIsOneLine() {
-        try (AutoCloseable flag = TestUtil.overrideFlag(ENABLE_TWOLINE_ALLAPPS, true)) {
-            // test string: "Battery Stats"
-            mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
-            mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW);
-            mBubbleTextView.applyLabel(mItemInfoWithIcon);
-            mBubbleTextView.setTypeface(Typeface.MONOSPACE);
-            mBubbleTextView.measure(mLimitedWidth, 0);
-            mBubbleTextView.onPreDraw();
-            assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+    public void testEnsurePredictionRowIsTwoLine() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        // test string: "Battery Stats"
+        mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW);
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+        mBubbleTextView.onPreDraw();
+
+        assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
+    }
+
+    @Test
+    public void modifyTitleToSupportMultiLine_whenLimitedHeight_shouldBeOneLine() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        // test string: "LEGO®Builder"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, LIMITED_HEIGHT);
+
+        mBubbleTextView.onPreDraw();
+
+        assertEquals(ONE_LINE, mBubbleTextView.getLineCount());
+    }
+
+    @Test
+    public void modifyTitleToSupportMultiLine_whenUnlimitedHeight_shouldBeTwoLine() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_ALLAPPS);
+        // test string: "LEGO®Builder"
+        mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
+        mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
+        mBubbleTextView.applyLabel(mItemInfoWithIcon);
+        mBubbleTextView.setTypeface(Typeface.MONOSPACE);
+        mBubbleTextView.measure(mLimitedWidth, MAX_HEIGHT);
+
+        mBubbleTextView.onPreDraw();
+
+        assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 8f2ac98..5417e3f 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -89,6 +89,7 @@
     public static void initialize(
             AbstractLauncherUiTest test, boolean clearWorkspace) throws Exception {
         test.reinitializeLauncherData(clearWorkspace);
+        test.mLauncher.resetFreezeRecentTaskList();
         test.mDevice.pressHome();
         test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
         test.waitForState("Launcher internal state didn't switch to Home",
diff --git a/tests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
new file mode 100644
index 0000000..61ec669
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
@@ -0,0 +1,30 @@
+package com.android.launcher3.util
+
+import com.android.launcher3.LauncherModel
+import com.android.launcher3.model.BgDataModel
+
+object ModelTestExtensions {
+    /** Clears and reloads Launcher db to cleanup the workspace */
+    fun LauncherModel.clearModelDb() {
+        // Load the model once so that there is no pending migration:
+        loadModelSync()
+        TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+            modelDbController.run {
+                tryMigrateDB()
+                createEmptyDB()
+                clearEmptyDbFlag()
+            }
+        }
+        // Reload model
+        TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { forceReload() }
+        loadModelSync()
+    }
+
+    fun LauncherModel.loadModelSync() {
+        val mockCb: BgDataModel.Callbacks = object : BgDataModel.Callbacks {}
+        TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { addCallbacksAndLoad(mockCb) }
+        TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {}
+        TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) {}
+        TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { removeCallbacks(mockCb) }
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 71ca77f..b7d2530 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -755,6 +755,17 @@
         return isTablet() ? getLauncherPackageName() : SYSTEMUI_PACKAGE;
     }
 
+    /**
+     * Resets the frozen recent tasks list if necessary from a previous quickswitch.
+     */
+    public void resetFreezeRecentTaskList() {
+        try {
+            mDevice.executeShellCommand("wm reset-freeze-recent-tasks");
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to reset fozen recent tasks list", e);
+        }
+    }
+
     private UiObject2 verifyContainerType(ContainerType containerType) {
         waitForLauncherInitialized();