Merge "Remove BubbleBarViewAnimatorTest from deviceless suite." into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 517bd6d..1bce9b3 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -59,6 +59,7 @@
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <action android:name="android.intent.action.SHOW_WORK_APPS" />
+                <action android:name="android.intent.action.ALL_APPS" />
                 <category android:name="android.intent.category.HOME" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.MONKEY"/>
diff --git a/go/AndroidManifest-launcher.xml b/go/AndroidManifest-launcher.xml
index 2223036..bef7180 100644
--- a/go/AndroidManifest-launcher.xml
+++ b/go/AndroidManifest-launcher.xml
@@ -57,6 +57,7 @@
             android:enabled="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.ALL_APPS" />
                 <category android:name="android.intent.category.HOME" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.MONKEY"/>
diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml
index 80d8154..d6aa886 100644
--- a/quickstep/AndroidManifest-launcher.xml
+++ b/quickstep/AndroidManifest-launcher.xml
@@ -57,6 +57,7 @@
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <action android:name="android.intent.action.SHOW_WORK_APPS" />
+                <action android:name="android.intent.action.ALL_APPS" />
                 <category android:name="android.intent.category.HOME" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.MONKEY"/>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index e9b4956..66887d0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -696,8 +696,7 @@
      */
     public WindowManager.LayoutParams createDefaultWindowLayoutParams(int type, String title) {
         int windowFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                | WindowManager.LayoutParams.FLAG_SLIPPERY
-                | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+                | WindowManager.LayoutParams.FLAG_SLIPPERY;
         boolean watchOutside = isTransientTaskbar() || isThreeButtonNav();
         if (watchOutside && !isRunningInTestHarness()) {
             windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
index b7000db..76489e0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
@@ -19,6 +19,7 @@
 import static android.view.MotionEvent.ACTION_HOVER_EXIT;
 import static android.view.View.ALPHA;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_ACTION_POPUP;
 import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS;
 
@@ -98,20 +99,18 @@
 
     @Override
     public boolean onHover(View v, MotionEvent event) {
-        boolean isFolderOpen = AbstractFloatingView.hasOpenView(mActivity, TYPE_FOLDER);
         // If hover leaves a taskbar icon animate the tooltip closed.
         if (event.getAction() == ACTION_HOVER_EXIT) {
             mHoverToolTipView.close(/* animate= */ false);
             mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, false);
-        } else if (!isFolderOpen && event.getAction() == ACTION_HOVER_ENTER) {
-            // Do not reveal if any floating views such as folders or edu pop-ups are open.
-            revealHoverToolTip();
+        } else if (event.getAction() == ACTION_HOVER_ENTER) {
+            maybeRevealHoverToolTip();
             mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, true);
         }
         return false;
     }
 
-    private void revealHoverToolTip() {
+    private void maybeRevealHoverToolTip() {
         if (mHoverView == null || mToolTipText == null) {
             return;
         }
@@ -122,6 +121,12 @@
         if (mHoverView instanceof FolderIcon && !((FolderIcon) mHoverView).getIconVisible()) {
             return;
         }
+        // Do not reveal if floating views such as folders or app pop-ups are open,
+        // as these views will overlap and not look great.
+        if (AbstractFloatingView.hasOpenView(mActivity, TYPE_FOLDER | TYPE_ACTION_POPUP)) {
+            return;
+        }
+
         Rect iconViewBounds = Utilities.getViewBounds(mHoverView);
         mHoverToolTipView.showAtLocation(mToolTipText, iconViewBounds.centerX(),
                 mTaskbarView.getTop() - mYOffset, /* shouldAutoClose= */ false);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 1a6cd60..7f240bd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -83,7 +83,7 @@
     // Initialized in init.
     private TaskbarControllers mControllers;
     private boolean mAllowInitialSplitSelection;
-    private AppInfo[] mAppInfosList;
+    private AppInfo[] mAppInfosList = AppInfo.EMPTY_ARRAY;
     // Saves the ItemInfos in the hotseat without the predicted items.
     private SparseArray<ItemInfo> mHotseatInfosList;
     private ManageWindowsTaskbarShortcut<BaseTaskbarContext> mManageWindowsTaskbarShortcut;
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
index 675d55b..de20f77 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
@@ -200,7 +200,7 @@
     private LayoutParams createLayoutParams() {
         LayoutParams layoutParams = new LayoutParams(
                 TYPE_APPLICATION_OVERLAY,
-                LayoutParams.FLAG_SPLIT_TOUCH,
+                /* flags = */ 0,
                 PixelFormat.TRANSLUCENT);
         layoutParams.setTitle(WINDOW_TITLE);
         layoutParams.gravity = Gravity.BOTTOM;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index 9970a7d..c7eedb0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -30,7 +30,6 @@
 import androidx.annotation.AnyThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
 
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.SafeCloseable;
@@ -167,7 +166,7 @@
         };
         QuickstepWidgetHolderListener holderListener = getHolderListener(appWidgetId);
         holderListener.addHolder(handler);
-        return () -> holderListener.mListeningHolders.remove(handler);
+        return () -> holderListener.removeHolder(handler);
     }
 
     /**
@@ -211,7 +210,7 @@
     public void clearViews() {
         mViews.clear();
         for (int i = sListeners.size() - 1; i >= 0; i--) {
-            sListeners.valueAt(i).mListeningHolders.remove(mUpdateHandler);
+            sListeners.valueAt(i).removeHolder(mUpdateHandler);
         }
     }
 
@@ -238,13 +237,15 @@
             mWidgetId = widgetId;
         }
 
-        @UiThread
-        @Nullable
         public RemoteViews addHolder(@NonNull UpdateHandler holder) {
-            mListeningHolders.add(holder);
+            MAIN_EXECUTOR.execute(() -> mListeningHolders.add(holder));
             return mRemoteViews;
         }
 
+        public void removeHolder(@NonNull UpdateHandler holder) {
+            MAIN_EXECUTOR.execute(() -> mListeningHolders.remove(holder));
+        }
+
         @Override
         @AnyThread
         public void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo info) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
index 454a307..76eb138 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
@@ -63,6 +63,7 @@
     private var hasDismissThresholdHapticRun = false
     private var initialDisplacement: Float = 0f
     private var recentsScaleAnimation: SpringAnimation? = null
+    private var isBlockedDuringDismissal = false
 
     private fun canInterceptTouch(ev: MotionEvent): Boolean =
         when {
@@ -137,6 +138,7 @@
     }
 
     override fun onDragStart(start: Boolean, startDisplacement: Float) {
+        if (isBlockedDuringDismissal) return
         val taskBeingDragged = taskBeingDragged ?: return
 
         initialDisplacement =
@@ -149,6 +151,7 @@
     }
 
     override fun onDrag(displacement: Float): Boolean {
+        if (isBlockedDuringDismissal) return true
         val taskBeingDragged = taskBeingDragged ?: return false
         val currentDisplacement = displacement + initialDisplacement
         val boundedDisplacement =
@@ -204,6 +207,7 @@
     }
 
     override fun onDragEnd(velocity: Float) {
+        if (isBlockedDuringDismissal) return
         val taskBeingDragged = taskBeingDragged ?: return
 
         val currentDisplacement =
@@ -234,6 +238,7 @@
                         if (isDismissing) (dismissLength * verticalFactor).toFloat() else 0f
                     )
                 }
+        isBlockedDuringDismissal = true
         recentsScaleAnimation =
             recentsView.animateRecentsScale(RECENTS_SCALE_DEFAULT).addEndListener { _, _, _, _ ->
                 recentsScaleAnimation = null
@@ -246,6 +251,7 @@
         taskBeingDragged?.translationZ = 0f
         taskBeingDragged = null
         springAnimation = null
+        isBlockedDuringDismissal = false
     }
 
     private fun getRecentsScale(dismissFraction: Float): Float {
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 4f00381..cf0a3d5 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -59,6 +59,7 @@
 
 import java.io.PrintWriter;
 import java.util.HashMap;
+import java.util.Locale;
 
 public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
     public static final boolean SHELL_TRANSITIONS_ROTATION =
@@ -132,6 +133,17 @@
     public RecentsAnimationCallbacks startRecentsAnimation(@NonNull GestureState gestureState,
             Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) {
         ActiveGestureProtoLogProxy.logStartRecentsAnimation();
+        // Check displayId
+        if (mDisplayId != gestureState.getDisplayId()) {
+            String msg = String.format(Locale.ENGLISH,
+                    "Constructor displayId %d does not equal gestureState display id %d",
+                    mDisplayId, gestureState.getDisplayId());
+            if (FeatureFlags.IS_STUDIO_BUILD) {
+                throw new IllegalArgumentException(msg);
+            } else {
+                Log.e("TaskAnimationManager", msg, new Exception());
+            }
+        }
         // Notify if recents animation is still running
         if (mController != null) {
             String msg = "New recents animation started before old animation completed";
@@ -239,11 +251,11 @@
                 RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
                 BaseContainerInterface containerInterface =
                         mLastGestureState.getContainerInterface();
-
                 for (RemoteAnimationTarget compat : appearedTaskTargets) {
                     if (compat.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME
                             && containerInterface.getCreatedContainer() instanceof RecentsActivity
-                            && DisplayController.getNavigationMode(mCtx) != NO_BUTTON) {
+                            && DisplayController.INSTANCE.get(mCtx).getInfoForDisplay(
+                            mDisplayId).getNavigationMode() != NO_BUTTON) {
                         // The only time we get onTasksAppeared() in button navigation with a
                         // 3p launcher is if the user goes to overview first, and in this case we
                         // can immediately finish the transition
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
index 7683a15..46ed29b 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
@@ -122,7 +122,7 @@
             field = max(value, minMaxWidth)
         }
 
-    var isExpanded: Boolean = false
+    var status: AppChipStatus = AppChipStatus.Collapsed
         private set
 
     override fun onFinishInflate() {
@@ -358,8 +358,8 @@
                 ObjectAnimator.ofFloat(iconArrowView, TRANSLATION_X, arrowTranslationWithRtl),
                 ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, -1f),
             )
-            animator!!.setDuration(MENU_BACKGROUND_REVEAL_DURATION.toLong())
-            isExpanded = true
+            animator!!.duration = MENU_BACKGROUND_REVEAL_DURATION.toLong()
+            status = AppChipStatus.Expanded
         } else {
             // Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu
             val expandedTextClipAnim =
@@ -393,8 +393,8 @@
                 ObjectAnimator.ofFloat(iconArrowView, TRANSLATION_X, 0f),
                 ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, 1f),
             )
-            animator!!.setDuration(MENU_BACKGROUND_HIDE_DURATION.toLong())
-            isExpanded = false
+            animator!!.duration = MENU_BACKGROUND_HIDE_DURATION.toLong()
+            status = AppChipStatus.Collapsed
         }
 
         if (!animated) animator!!.duration = 0
@@ -434,6 +434,11 @@
 
     override fun asView(): View = this
 
+    enum class AppChipStatus {
+        Expanded,
+        Collapsed,
+    }
+
     private companion object {
         private val SUM_AGGREGATOR = FloatBiFunction { a: Float, b: Float -> a + b }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
index d39b528..db18394 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
@@ -82,6 +82,7 @@
                             runTaskGridReflowSpringAnimation(
                                 draggedTaskView,
                                 getDismissedTaskGapForReflow(draggedTaskView),
+                                onEndRunnable,
                             )
                         } else {
                             recentsView.dismissTaskView(
@@ -89,11 +90,12 @@
                                 /* animateTaskView = */ false,
                                 /* removeTask = */ true,
                             )
+                            onEndRunnable()
                         }
                     } else {
                         recentsView.onDismissAnimationEnds()
+                        onEndRunnable()
                     }
-                    onEndRunnable()
                 }
         if (!isDismissing) {
             addNeighborSettlingSpringAnimations(
@@ -295,7 +297,7 @@
         val maxDismissSettlingVelocity =
             recentsView.pagedOrientationHandler.getSecondaryDimension(recentsView)
         MSDLPlayerWrapper.INSTANCE.get(recentsView.context)
-            .playToken(
+            ?.playToken(
                 MSDLToken.CANCEL,
                 InteractionProperties.DynamicVibrationScale(
                     boundToRange(abs(velocity) / maxDismissSettlingVelocity, 0f, 1f),
@@ -339,6 +341,7 @@
     private fun runTaskGridReflowSpringAnimation(
         dismissedTaskView: TaskView,
         dismissedTaskGap: Float,
+        onEndRunnable: () -> Unit,
     ) {
         // Empty spring animation exists for conditional start, and to drive neighboring springs.
         val springAnimationDriver =
@@ -427,12 +430,17 @@
                 driverProgressThreshold = dismissedTaskGap,
                 isSpringDirectionVertical = false,
             )
+        } else {
+            springAnimationDriver.addEndListener { _, _, _, _ ->
+                // Play the same haptic as when neighbors spring into place.
+                MSDLPlayerWrapper.INSTANCE.get(recentsView.context)?.playToken(MSDLToken.CANCEL)
+            }
         }
 
         // Start animations and remove the dismissed task at the end, dismiss immediately if no
         // neighboring tasks exist.
         val runGridEndAnimationAndRelayout = {
-            recentsView.expressiveDismissTaskView(dismissedTaskView)
+            recentsView.expressiveDismissTaskView(dismissedTaskView, onEndRunnable)
         }
         springAnimationDriver?.apply {
             addEndListener { _, _, _, _ -> runGridEndAnimationAndRelayout() }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 37c75cd..4feeb95 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3367,8 +3367,8 @@
         int snappedPage = isKeyboardTaskFocusPending() ? mKeyboardTaskFocusIndex : getNextPage();
         TaskView snappedTaskView = getTaskViewAt(snappedPage);
         TaskView homeTaskView = getHomeTaskView();
-        TaskView expectedCurrentTaskView = mUtils.getExpectedCurrentTask(getFocusedTaskView(),
-                getRunningTaskView());
+        TaskView expectedCurrentTaskView = mUtils.getExpectedCurrentTask(getRunningTaskView(),
+                getFocusedTaskView());
         TaskView nextFocusedTaskView = null;
 
         // Don't clear the top row, if the user has dismissed a task, to maintain the task order.
@@ -3943,7 +3943,7 @@
                 newClearAllShortTotalWidthTranslation = expectedFirstTaskStart - firstTaskStart;
             }
         }
-        if (lastGridTaskView != null && (lastGridTaskView.isVisibleToUser() || (
+        if (lastGridTaskView != null && (isTaskViewVisible(lastGridTaskView) || (
                 isExpressiveDismiss && lastGridTaskView == dismissedTaskView))) {
             // After dismissal, animate translation of the remaining tasks to fill any gap left
             // between the end of the grid and the clear all button. Only animate if the clear
@@ -4740,11 +4740,12 @@
         runDismissAnimation(pa);
     }
 
-    protected void expressiveDismissTaskView(TaskView taskView) {
+    protected void expressiveDismissTaskView(TaskView taskView, Function0<Unit> onEndRunnable) {
         PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION);
         createTaskDismissAnimation(pa, taskView, false /* animateTaskView */, true /* removeTask */,
                 DISMISS_TASK_DURATION, false /* dismissingForSplitSelection*/,
                 true /* isExpressiveDismiss */);
+        pa.addEndListener((success) -> onEndRunnable.invoke());
         runDismissAnimation(pa);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
index 696f934..4d4ce4e 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
@@ -428,9 +428,7 @@
 
         var additionalTranslationX = 0f
         if (
-            recentsViewContainer.deviceProfile.isLandscape &&
-                taskContainer.stagePosition ==
-                    SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+            taskContainer.stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
         ) {
             // Animate menu and icon when split task would display off the side of the screen.
             additionalTranslationX =
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 3835e48..e6ef708 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -99,6 +99,7 @@
 import com.android.quickstep.util.TaskRemovedDuringLaunchListener
 import com.android.quickstep.util.displayId
 import com.android.quickstep.util.isExternalDisplay
+import com.android.quickstep.views.IconAppChipView.AppChipStatus
 import com.android.quickstep.views.RecentsView.UNBOUND_TASK_VIEW_ID
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
@@ -1551,7 +1552,7 @@
             recentsView.setTaskBorderEnabled(false)
         }
         return if (enableOverviewIconMenu() && menuContainer.iconView is IconAppChipView) {
-            if (menuContainer.iconView.isExpanded) {
+            if (menuContainer.iconView.status == AppChipStatus.Expanded) {
                 closeTaskMenu()
             } else {
                 menuContainer.iconView.revealAnim(/* isRevealing= */ true)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.kt
new file mode 100644
index 0000000..d2b9fcf
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar
+
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_HOVER_ENTER
+import android.view.MotionEvent.ACTION_HOVER_EXIT
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.R
+import com.android.launcher3.apppairs.AppPairIcon
+import com.android.launcher3.folder.FolderIcon
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatAppPairsItem
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatFolderItem
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatWorkspaceItem
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
+class TaskbarHoverToolTipControllerTest {
+
+    @get:Rule(order = 0) val context = TaskbarWindowSandboxContext.create()
+    @get:Rule(order = 1) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+
+    @InjectController lateinit var autohideSuspendController: TaskbarAutohideSuspendController
+    @InjectController lateinit var popupController: TaskbarPopupController
+
+    private val taskbarContext: TaskbarActivityContext by taskbarUnitTestRule::activityContext
+
+    private lateinit var taskbarView: TaskbarView
+    private lateinit var iconView: BubbleTextView
+    private lateinit var appPairIcon: AppPairIcon
+    private lateinit var folderIcon: FolderIcon
+
+    private val isHoverToolTipOpen: Boolean
+        get() {
+            // TaskbarHoverToolTip uses ArrowTipView which is type TYPE_ON_BOARD_POPUP.
+            return AbstractFloatingView.hasOpenView(
+                taskbarContext,
+                AbstractFloatingView.TYPE_ON_BOARD_POPUP,
+            )
+        }
+
+    @Before
+    fun setup() {
+        runOnMainSync { taskbarView = taskbarContext.dragLayer.findViewById(R.id.taskbar_view) }
+
+        val hotseatItems =
+            arrayOf(
+                createHotseatWorkspaceItem(),
+                createHotseatAppPairsItem(),
+                createHotseatFolderItem(),
+            )
+        runOnMainSync {
+            taskbarView.updateItems(hotseatItems, emptyList())
+            iconView =
+                taskbarView.iconViews.filterIsInstance<BubbleTextView>().first {
+                    it.tag is WorkspaceItemInfo
+                }
+            appPairIcon = taskbarView.iconViews.filterIsInstance<AppPairIcon>().first()
+            folderIcon = taskbarView.iconViews.filterIsInstance<FolderIcon>().first()
+        }
+    }
+
+    @Test
+    fun onHover_hoverEnterIcon_revealToolTip_hoverExitIcon_closeToolTip() {
+        runOnMainSync { iconView.dispatchGenericMotionEvent(HOVER_ENTER) }
+        assertThat(isHoverToolTipOpen).isTrue()
+        assertThat(autohideSuspendController.isTransientTaskbarStashingSuspended).isTrue()
+        runOnMainSync { iconView.dispatchGenericMotionEvent(HOVER_EXIT) }
+        assertThat(isHoverToolTipOpen).isFalse()
+        assertThat(autohideSuspendController.isTransientTaskbarStashingSuspended).isFalse()
+    }
+
+    @Test
+    fun onHover_hoverEnterFolderIcon_revealToolTip_hoverExitFolderIcon_closeToolTip() {
+        runOnMainSync { folderIcon.dispatchGenericMotionEvent(HOVER_ENTER) }
+        assertThat(isHoverToolTipOpen).isTrue()
+        assertThat(autohideSuspendController.isTransientTaskbarStashingSuspended).isTrue()
+        runOnMainSync { folderIcon.dispatchGenericMotionEvent(HOVER_EXIT) }
+        assertThat(isHoverToolTipOpen).isFalse()
+        assertThat(autohideSuspendController.isTransientTaskbarStashingSuspended).isFalse()
+    }
+
+    @Test
+    fun onHover_hoverEnterAppPair_revealToolTip_hoverExitAppPair_closeToolTip() {
+        runOnMainSync { appPairIcon.dispatchGenericMotionEvent(HOVER_ENTER) }
+        assertThat(isHoverToolTipOpen).isTrue()
+        assertThat(autohideSuspendController.isTransientTaskbarStashingSuspended).isTrue()
+        runOnMainSync { appPairIcon.dispatchGenericMotionEvent(HOVER_EXIT) }
+        assertThat(isHoverToolTipOpen).isFalse()
+        assertThat(autohideSuspendController.isTransientTaskbarStashingSuspended).isFalse()
+    }
+
+    @Test
+    fun onHover_hoverEnterIconAlignedWithHotseat_noToolTip() {
+        taskbarContext.setUIController(
+            object : TaskbarUIController() {
+                override fun isIconAlignedWithHotseat(): Boolean = true
+            }
+        )
+
+        runOnMainSync { iconView.dispatchGenericMotionEvent(HOVER_ENTER) }
+        assertThat(isHoverToolTipOpen).isFalse()
+    }
+
+    @Test
+    fun onHover_hoverEnterFolderOpen_noToolTip() {
+        runOnMainSync {
+            folderIcon.folder.animateOpen()
+            iconView.dispatchGenericMotionEvent(HOVER_ENTER)
+        }
+        assertThat(isHoverToolTipOpen).isFalse()
+    }
+
+    @Test
+    fun onHover_hoverEnterPopupOpen_noToolTip() {
+        runOnMainSync {
+            popupController.showForIcon(iconView)
+            iconView.dispatchGenericMotionEvent(HOVER_ENTER)
+        }
+        assertThat(isHoverToolTipOpen).isFalse()
+    }
+
+    companion object {
+        private val HOVER_EXIT = MotionEvent.obtain(0, 0, ACTION_HOVER_EXIT, 0f, 0f, 0)
+        private val HOVER_ENTER = MotionEvent.obtain(0, 0, ACTION_HOVER_ENTER, 0f, 0f, 0)
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
index e52aacf..92abbba 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
@@ -18,8 +18,13 @@
 
 import android.content.ComponentName
 import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Bitmap.createBitmap
 import android.os.Process
+import com.android.launcher3.icons.BitmapInfo
 import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.AppPairInfo
+import com.android.launcher3.model.data.FolderInfo
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.taskbar.TaskbarIconType.ALL_APPS
@@ -53,7 +58,27 @@
         return WorkspaceItemInfo(
                 AppInfo(TEST_COMPONENT, "Test App $id", Process.myUserHandle(), Intent())
             )
-            .apply { this.id = id }
+            .apply {
+                this.id = id
+                // Create a placeholder icon so that the test  doesn't try to load a high-res icon.
+                this.bitmap = BitmapInfo.fromBitmap(createBitmap(1, 1, Bitmap.Config.ALPHA_8))
+            }
+    }
+
+    fun createHotseatAppPairsItem(): AppPairInfo {
+        return AppPairInfo().apply {
+            add(createHotseatWorkspaceItem(1))
+            add(createHotseatWorkspaceItem(2))
+        }
+    }
+
+    fun createHotseatFolderItem(): FolderInfo {
+        return FolderInfo().apply {
+            title = "Test Folder"
+            add(createHotseatWorkspaceItem(1))
+            add(createHotseatWorkspaceItem(2))
+            add(createHotseatWorkspaceItem(3))
+        }
     }
 
     /** Creates a list of fake recent tasks. */
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
index 2dacf69..19c8824 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
@@ -28,6 +28,7 @@
 import com.android.launcher3.taskbar.TaskbarControllers
 import com.android.launcher3.taskbar.TaskbarManager
 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks
+import com.android.launcher3.taskbar.TaskbarUIController
 import com.android.launcher3.taskbar.bubbles.BubbleControllers
 import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
 import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
@@ -117,6 +118,8 @@
                                 super.recreateTaskbars()
                                 if (currentActivityContext != null) {
                                     injectControllers()
+                                    // TODO(b/346394875): we should test a non-default uiController.
+                                    activityContext.setUIController(TaskbarUIController.DEFAULT)
                                     controllerInjectionCallback.invoke()
                                 }
                             }
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
deleted file mode 100644
index 3f7c85c..0000000
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * 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.taskbar;
-
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
-import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.Display;
-import android.view.MotionEvent;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.apppairs.AppPairIcon;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.util.ActivityContextWrapper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-/**
- * Tests for TaskbarHoverToolTipController.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class TaskbarHoverToolTipControllerTest extends TaskbarBaseTestCase {
-
-    private TaskbarHoverToolTipController mTaskbarHoverToolTipController;
-    private TestableLooper mTestableLooper;
-
-    @Mock private TaskbarView mTaskbarView;
-    @Mock private MotionEvent mMotionEvent;
-    @Mock private BubbleTextView mHoverBubbleTextView;
-    @Mock private FolderIcon mHoverFolderIcon;
-    @Mock private AppPairIcon mAppPairIcon;
-    @Mock private Display mDisplay;
-    @Mock private TaskbarDragLayer mTaskbarDragLayer;
-    private Folder mSpyFolderView;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        Context context = getApplicationContext();
-
-        doAnswer((Answer<Object>) invocation -> context.getSystemService(
-                (String) invocation.getArgument(0)))
-                .when(taskbarActivityContext).getSystemService(anyString());
-        when(taskbarActivityContext.getResources()).thenReturn(context.getResources());
-        when(taskbarActivityContext.getApplicationInfo()).thenReturn(
-                context.getApplicationInfo());
-        when(taskbarActivityContext.getDragLayer()).thenReturn(mTaskbarDragLayer);
-        when(taskbarActivityContext.getMainLooper()).thenReturn(context.getMainLooper());
-        when(taskbarActivityContext.getDisplay()).thenReturn(mDisplay);
-        when(taskbarActivityContext.isIconAlignedWithHotseat()).thenReturn(false);
-
-        when(mTaskbarDragLayer.getChildCount()).thenReturn(1);
-        mSpyFolderView = spy(new Folder(new ActivityContextWrapper(context), null));
-        when(mTaskbarDragLayer.getChildAt(anyInt())).thenReturn(mSpyFolderView);
-        doReturn(false).when(mSpyFolderView).isOpen();
-
-        when(mHoverBubbleTextView.getText()).thenReturn("tooltip");
-        doAnswer((Answer<Void>) invocation -> {
-            Object[] args = invocation.getArguments();
-            ((int[]) args[0])[0] = 0;
-            ((int[]) args[0])[1] = 0;
-            return null;
-        }).when(mHoverBubbleTextView).getLocationOnScreen(any(int[].class));
-        when(mHoverBubbleTextView.getWidth()).thenReturn(100);
-        when(mHoverBubbleTextView.getHeight()).thenReturn(100);
-
-        mHoverFolderIcon.mInfo = new FolderInfo();
-        mHoverFolderIcon.mInfo.title = "tooltip";
-        doAnswer((Answer<Void>) invocation -> {
-            Object[] args = invocation.getArguments();
-            ((int[]) args[0])[0] = 0;
-            ((int[]) args[0])[1] = 0;
-            return null;
-        }).when(mHoverFolderIcon).getLocationOnScreen(any(int[].class));
-        when(mHoverFolderIcon.getWidth()).thenReturn(100);
-        when(mHoverFolderIcon.getHeight()).thenReturn(100);
-
-        when(mTaskbarView.getTop()).thenReturn(200);
-
-        mTaskbarHoverToolTipController = new TaskbarHoverToolTipController(
-                taskbarActivityContext, mTaskbarView, mHoverBubbleTextView);
-        mTestableLooper = TestableLooper.get(this);
-    }
-
-    @Test
-    public void onHover_hoverEnterIcon_revealToolTip() {
-        when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
-        when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
-
-        boolean hoverConsumed =
-                mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
-        waitForIdleSync();
-
-        assertThat(hoverConsumed).isFalse();
-        verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
-                true);
-    }
-
-    @Test
-    public void onHover_hoverExitIcon_closeToolTip() {
-        when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
-        when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
-
-        boolean hoverConsumed =
-                mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
-        waitForIdleSync();
-
-        assertThat(hoverConsumed).isFalse();
-        verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
-                false);
-    }
-
-    @Test
-    public void onHover_hoverEnterFolderIcon_revealToolTip() {
-        when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
-        when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
-
-        boolean hoverConsumed =
-                mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
-        waitForIdleSync();
-
-        assertThat(hoverConsumed).isFalse();
-        verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
-                true);
-    }
-
-    @Test
-    public void onHover_hoverExitFolderIcon_closeToolTip() {
-        when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
-        when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
-
-        boolean hoverConsumed =
-                mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
-        waitForIdleSync();
-
-        assertThat(hoverConsumed).isFalse();
-        verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
-                false);
-    }
-
-    @Test
-    public void onHover_hoverExitFolderOpen_closeToolTip() {
-        when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
-        when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
-        doReturn(true).when(mSpyFolderView).isOpen();
-
-        boolean hoverConsumed =
-                mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
-        waitForIdleSync();
-
-        assertThat(hoverConsumed).isFalse();
-        verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
-                false);
-    }
-
-    @Test
-    public void onHover_hoverEnterFolderOpen_noToolTip() {
-        when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
-        when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
-        doReturn(true).when(mSpyFolderView).isOpen();
-
-        boolean hoverConsumed =
-                mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
-
-        assertThat(hoverConsumed).isFalse();
-    }
-
-    @Test
-    public void onHover_hoverMove_noUpdate() {
-        when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_MOVE);
-        when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_MOVE);
-
-        boolean hoverConsumed =
-                mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
-
-        assertThat(hoverConsumed).isFalse();
-    }
-
-    @Test
-    public void onHover_hoverEnterAppPair_revealToolTip() {
-        when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
-        when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
-
-        boolean hoverConsumed =
-                mTaskbarHoverToolTipController.onHover(mAppPairIcon, mMotionEvent);
-        waitForIdleSync();
-
-        assertThat(hoverConsumed).isFalse();
-        verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
-                true);
-    }
-
-    @Test
-    public void onHover_hoverExitAppPair_closeToolTip() {
-        when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
-        when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
-
-        boolean hoverConsumed =
-                mTaskbarHoverToolTipController.onHover(mAppPairIcon, mMotionEvent);
-        waitForIdleSync();
-
-        assertThat(hoverConsumed).isFalse();
-        verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
-                false);
-    }
-
-    @Test
-    public void onHover_hoverEnterIconAlignedWithHotseat_noReveal() {
-        when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
-        when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
-        when(taskbarActivityContext.isIconAlignedWithHotseat()).thenReturn(true);
-
-        boolean hoverConsumed =
-                mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
-        waitForIdleSync();
-
-        assertThat(hoverConsumed).isFalse();
-        verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
-                true);
-    }
-
-    private void waitForIdleSync() {
-        mTestableLooper.processAllMessages();
-    }
-}
diff --git a/res/values-sw600dp/styles.xml b/res/values-sw600dp/styles.xml
index 63bd46b..db49a3e 100644
--- a/res/values-sw600dp/styles.xml
+++ b/res/values-sw600dp/styles.xml
@@ -14,8 +14,11 @@
   ~ limitations under the License.
   -->
 
-<resources>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
     <style name="CellStyleDefault">
         <item name="iconDrawablePadding">7dp</item>
     </style>
+    <style name="DropTargetButton" parent="DropTargetButtonBase">
+        <item name="android:fontFamily" android:featureFlag="com.android.launcher3.gsf_res">variable-title-large</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 39206d3..cf6c560 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -373,7 +373,9 @@
         <item name="android:background">@drawable/drop_target_background</item>
     </style>
 
-    <style name="DropTargetButton" parent="DropTargetButtonBase" />
+    <style name="DropTargetButton" parent="DropTargetButtonBase">
+        <item name="android:fontFamily" android:featureFlag="com.android.launcher3.gsf_res">variable-title-medium</item>
+    </style>
 
     <style name="TextHeadline" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" />
 
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 39f68bf..9a226df 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -24,7 +24,9 @@
 import static com.android.launcher3.allapps.AlphabeticalAppsList.PRIVATE_SPACE_PACKAGE;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
+import static com.android.launcher3.shortcuts.DeepShortcutTextView.GOOGLE_SANS_FLEX_LABEL_LARGE;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.wm.shell.Flags.enableGsf;
 
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
@@ -32,6 +34,7 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.Typeface;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
@@ -479,6 +482,10 @@
         if (view instanceof DeepShortcutView) {
             // System shortcut takes entire row with icon and text
             final DeepShortcutView shortcutView = (DeepShortcutView) view;
+            if (enableGsf()) {
+                shortcutView.getBubbleText().setTypeface(
+                        Typeface.create(GOOGLE_SANS_FLEX_LABEL_LARGE, Typeface.NORMAL));
+            }
             info.setIconAndLabelFor(shortcutView.getIconView(), shortcutView.getBubbleText());
         } else if (view instanceof ImageView) {
             // System shortcut is just an icon
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
index ded2cee..b1d095b 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
@@ -16,9 +16,12 @@
 
 package com.android.launcher3.shortcuts;
 
+import static com.android.wm.shell.Flags.enableGsf;
+
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -31,6 +34,7 @@
  * A {@link BubbleTextView} that has the shortcut icon on the left and drag handle on the right.
  */
 public class DeepShortcutTextView extends BubbleTextView {
+    public static final String GOOGLE_SANS_FLEX_LABEL_LARGE = "variable-label-large";
 
     private boolean mShowLoadingState;
     private Drawable mLoadingStatePlaceholder;
@@ -47,6 +51,9 @@
     public DeepShortcutTextView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         showLoadingState(true);
+        if (enableGsf()) {
+            setTypeface(Typeface.create(GOOGLE_SANS_FLEX_LABEL_LARGE, Typeface.NORMAL));
+        }
     }
 
     @Override