Merge "Converting more pressBack calls to calls that wait for animation" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index af175ce..210cfd0 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -43,6 +43,13 @@
 }
 
 flag {
+    name: "enable_focus_outline"
+    namespace: "launcher"
+    description: "Enables focus states outline for launcher."
+    bug: "310953377"
+}
+
+flag {
     name: "enable_taskbar_no_recreate"
     namespace: "launcher"
     description: "Enables taskbar with no recreation from lifecycle changes of TaskbarActivityContext."
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index bb2f533..893b1c8 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -86,7 +86,7 @@
     <string name="gesture_tutorial_nice" msgid="2936275692616928280">"¡Muy bien!"</string>
     <string name="gesture_tutorial_step" msgid="1279786122817620968">"Tutorial <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
     <string name="allset_title" msgid="5021126669778966707">"¡Ya está!"</string>
-    <string name="allset_hint" msgid="459504134589971527">"Desliza el dedo hacia arriba para ir a la pantalla de inicio"</string>
+    <string name="allset_hint" msgid="459504134589971527">"Desliza hacia arriba para ir a la pantalla de inicio"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Toca el botón de inicio para ir a la pantalla de inicio"</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"Ya puedes empezar a usar tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="default_device_name" msgid="6660656727127422487">"dispositivo"</string>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index c8da620..91e4ad5 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -109,7 +109,7 @@
     <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"برای استفاده هم‌زمان از ۲ برنامه، یک برنامه را به کناری بکشید"</string>
     <string name="taskbar_edu_stashing" msgid="5645461372669217294">"برای نمایش «نوار وظیفه»، انگشتتان را آهسته به‌بالا بکشید"</string>
     <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"براساس روال‌هایتان، پیشنهاد برنامه دریافت کنید"</string>
-    <string name="taskbar_edu_pinning" msgid="6708550858580071558">"برای سنجاق کردن «نوار وظیفه»، جداکننده را چند ثانیه فشار دهید"</string>
+    <string name="taskbar_edu_pinning" msgid="6708550858580071558">"جداکننده را چند ثانیه فشار دهید تا «نوار وظیفه» سنجاق شود"</string>
     <string name="taskbar_edu_features" msgid="3320337287472848162">"با «نوار وظیفه» می‌توانید کارهای بیشتر انجام دهید"</string>
     <string name="taskbar_edu_close" msgid="887022990168191073">"بستن"</string>
     <string name="taskbar_edu_done" msgid="6880178093977704569">"تمام"</string>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index df6cad4..789ef13 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -109,7 +109,7 @@
     <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Faites glisser une appli sur le côté pour en utiliser 2 à la fois"</string>
     <string name="taskbar_edu_stashing" msgid="5645461372669217294">"Balayez lentement vers haut pour afficher barre des tâches"</string>
     <string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Obtenez des suggestions d\'applis basées sur vos habitudes"</string>
-    <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Appui de manière prolongée sur le séparateur pour épingler la barre des tâches"</string>
+    <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Appui prolongé sur le séparateur pour épingler la barre des tâches"</string>
     <string name="taskbar_edu_features" msgid="3320337287472848162">"Faites-en plus avec la barre des tâches"</string>
     <string name="taskbar_edu_close" msgid="887022990168191073">"Fermer"</string>
     <string name="taskbar_edu_done" msgid="6880178093977704569">"OK"</string>
diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml
index df32626..29779a7 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -35,4 +35,6 @@
 
   <string name="assist_state_manager_class" translatable="false"></string>
 
+  <string name="launcher_restore_event_logger_class" translatable="false">com.android.quickstep.LauncherRestoreEventLoggerImpl</string>
+
 </resources>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 4898761..f25b652 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -64,8 +64,6 @@
 import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
-import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME;
-import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME_FALLBACK;
 import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
 import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
 
@@ -117,6 +115,7 @@
 import androidx.annotation.Nullable;
 import androidx.core.graphics.ColorUtils;
 
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
 import com.android.launcher3.anim.AnimationSuccessListener;
@@ -1652,7 +1651,8 @@
         // is initialized.
         if (launcherIsForceInvisibleOrOpening) {
             addCujInstrumentation(anim, playFallBackAnimation
-                    ? CUJ_APP_CLOSE_TO_HOME_FALLBACK : CUJ_APP_CLOSE_TO_HOME);
+                    ? Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK
+                    : Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
 
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
@@ -1768,19 +1768,18 @@
             if (launchingFromWidget) {
                 composeWidgetLaunchAnimator(anim, (LauncherAppWidgetHostView) mV, appTargets,
                         wallpaperTargets, nonAppTargets, launcherClosing);
-                addCujInstrumentation(
-                        anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_WIDGET);
+                addCujInstrumentation(anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET);
                 skipFirstFrame = true;
             } else if (launchingFromRecents) {
                 composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,
                         launcherClosing);
                 addCujInstrumentation(
-                        anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_RECENTS);
+                        anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS);
                 skipFirstFrame = true;
             } else {
                 composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,
                         launcherClosing);
-                addCujInstrumentation(anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_ICON);
+                addCujInstrumentation(anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON);
                 skipFirstFrame = false;
             }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index bed4c37..709d3ba 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -25,8 +25,6 @@
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
-import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
-import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
@@ -239,7 +237,7 @@
         Point p = !mContext.isUserSetupComplete()
                 ? new Point(0, mControllers.taskbarActivityContext.getSetupWindowHeight())
                 : DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
-                        TaskbarManager.isPhoneMode(deviceProfile));
+                        mContext.isPhoneMode());
         mNavButtonsView.getLayoutParams().height = p.y;
 
         mIsImeRenderingNavButtons =
@@ -305,7 +303,7 @@
             initButtons(mNavButtonContainer, mEndContextualContainer,
                     mControllers.navButtonController);
             updateButtonLayoutSpacing();
-            updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneButtonNavMode(mContext));
+            updateStateForFlag(FLAG_SMALL_SCREEN, mContext.isPhoneButtonNavMode());
 
             mPropertyHolders.add(new StatePropertyHolder(
                     mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
@@ -388,7 +386,7 @@
         int navButtonSize = mContext.getResources().getDimensionPixelSize(
                 R.dimen.taskbar_nav_buttons_size);
         boolean isRtl = Utilities.isRtl(mContext.getResources());
-        if (!isPhoneMode(mContext.getDeviceProfile())) {
+        if (!mContext.isPhoneMode()) {
             mPropertyHolders.add(new StatePropertyHolder(
                     mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
                             || (flags & FLAG_KEYGUARD_VISIBLE) != 0,
@@ -632,7 +630,7 @@
      * Sets the translationY of the nav buttons based on the current device state.
      */
     public void updateNavButtonTranslationY() {
-        if (isPhoneButtonNavMode(mContext)) {
+        if (mContext.isPhoneButtonNavMode()) {
             return;
         }
         final float normalTranslationY = mTaskbarNavButtonTranslationY.value;
@@ -751,9 +749,8 @@
                             dp, mNavButtonsView, mImeSwitcherButton,
                             mControllers.rotationButtonController.getRotationButton(),
                             mA11yButton, res, isInKidsMode, isInSetup, isThreeButtonNav,
-                            TaskbarManager.isPhoneMode(dp),
-                            mWindowManagerProxy.getRotation(mContext));
-            navButtonLayoutter.layoutButtons(dp, isA11yButtonPersistent());
+                            mContext.isPhoneMode(), mWindowManagerProxy.getRotation(mContext));
+            navButtonLayoutter.layoutButtons(mContext, isA11yButtonPersistent());
             updateNavButtonColor();
             return;
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index da1f766..ad2dc23 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -107,7 +107,7 @@
         mControllers = controllers;
         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
         Resources resources = mActivity.getResources();
-        if (isPhoneGestureNavMode(mActivity.getDeviceProfile())) {
+        if (mActivity.isPhoneGestureNavMode()) {
             mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_phone_size);
             mStashedHandleWidth =
                     resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
@@ -120,7 +120,7 @@
         mStashedHandleView.getLayoutParams().height = mTaskbarSize + taskbarBottomMargin;
 
         mTaskbarStashedHandleAlpha.get(ALPHA_INDEX_STASHED).setValue(
-                isPhoneGestureNavMode(deviceProfile) ? 1 : 0);
+                mActivity.isPhoneGestureNavMode() ? 1 : 0);
         mTaskbarStashedHandleHintScale.updateValue(1f);
 
         final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight();
@@ -148,7 +148,7 @@
             view.setPivotY(stashedCenterY);
         });
         initRegionSampler();
-        if (isPhoneGestureNavMode(deviceProfile)) {
+        if (mActivity.isPhoneGestureNavMode()) {
             onIsStashedChanged(true);
         }
     }
@@ -184,10 +184,6 @@
         mRegionSamplingHelper = null;
     }
 
-    private boolean isPhoneGestureNavMode(DeviceProfile deviceProfile) {
-        return TaskbarManager.isPhoneMode(deviceProfile) && !mActivity.isThreeButtonNav();
-    }
-
     public MultiPropertyFactory<View> getStashedHandleAlpha() {
         return mTaskbarStashedHandleAlpha;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 4290948..f6703f3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -214,7 +214,7 @@
         Context c = getApplicationContext();
         mWindowManager = c.getSystemService(WindowManager.class);
 
-        boolean phoneMode = TaskbarManager.isPhoneMode(mDeviceProfile);
+        boolean phoneMode = isPhoneMode();
         mLeftCorner = phoneMode
                 ? null
                 : display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
@@ -388,6 +388,28 @@
     }
 
     /**
+     * @return {@code true} if the device profile isn't a large screen profile and we are using a
+     * single window for taskbar and navbar.
+     */
+    public boolean isPhoneMode() {
+        return ENABLE_TASKBAR_NAVBAR_UNIFICATION && mDeviceProfile.isPhone;
+    }
+
+    /**
+     * @return {@code true} if {@link #isPhoneMode()} is true and we're using 3 button-nav
+     */
+    public boolean isPhoneButtonNavMode() {
+        return isPhoneMode() && isThreeButtonNav();
+    }
+
+    /**
+     * @return {@code true} if {@link #isPhoneMode()} is true and we're using gesture nav
+     */
+    public boolean isPhoneGestureNavMode() {
+        return isPhoneMode() && !isThreeButtonNav();
+    }
+
+    /**
      * Show Taskbar upon receiving broadcast
      */
     public void showTaskbarFromBroadcast() {
@@ -464,9 +486,7 @@
         windowLayoutParams.privateFlags =
                 WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
         windowLayoutParams.accessibilityTitle = getString(
-                TaskbarManager.isPhoneMode(mDeviceProfile)
-                        ? R.string.taskbar_phone_a11y_title
-                        : R.string.taskbar_a11y_title);
+                isPhoneMode() ? R.string.taskbar_phone_a11y_title : R.string.taskbar_a11y_title);
 
         return windowLayoutParams;
     }
@@ -480,8 +500,7 @@
                 ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL;
         WindowManager.LayoutParams windowLayoutParams =
                 createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE);
-        boolean isPhoneNavMode = TaskbarManager.isPhoneButtonNavMode(this);
-        if (!isPhoneNavMode) {
+        if (!isPhoneButtonNavMode()) {
             return windowLayoutParams;
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index d6016f1..30f8d56 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -23,7 +23,6 @@
 import android.graphics.Path
 import android.graphics.RectF
 import com.android.app.animation.Interpolators
-import com.android.launcher3.DeviceProfile
 import com.android.launcher3.R
 import com.android.launcher3.Utilities
 import com.android.launcher3.Utilities.mapRange
@@ -95,10 +94,10 @@
         setCornerRoundness(DEFAULT_ROUNDNESS)
     }
 
-    fun updateStashedHandleWidth(dp: DeviceProfile, res: Resources) {
+    fun updateStashedHandleWidth(context: TaskbarActivityContext, res: Resources) {
         stashedHandleWidth =
             res.getDimensionPixelSize(
-                if (TaskbarManager.isPhoneMode(dp)) R.dimen.taskbar_stashed_small_screen
+                if (context.isPhoneMode) R.dimen.taskbar_stashed_small_screen
                 else R.dimen.taskbar_stashed_handle_width
             )
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index a24cf4b..f9fc983 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -106,7 +106,7 @@
 
     public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
         mControllerCallbacks = callbacks;
-        mBackgroundRenderer.updateStashedHandleWidth(mActivity.getDeviceProfile(), getResources());
+        mBackgroundRenderer.updateStashedHandleWidth(mActivity, getResources());
         recreateControllers();
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 73e32ab..3823c5a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -276,11 +276,10 @@
          */
         public int getTaskbarBackgroundHeight() {
             DeviceProfile deviceProfile = mActivity.getDeviceProfile();
-            if (TaskbarManager.isPhoneMode(deviceProfile)) {
+            if (mActivity.isPhoneMode()) {
                 Resources resources = mActivity.getResources();
-                Point taskbarDimensions =
-                        DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
-                                TaskbarManager.isPhoneMode(deviceProfile));
+                Point taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(deviceProfile,
+                        resources, true /* isPhoneMode */);
                 return taskbarDimensions.y == -1 ?
                         deviceProfile.getDisplayInfo().currentSize.y :
                         taskbarDimensions.y;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index 6d1b558..4776f0c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -32,7 +32,6 @@
 import com.android.launcher3.config.FeatureFlags.enableTaskbarPinningEdu
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
-import com.android.launcher3.taskbar.TaskbarManager.isPhoneMode
 import com.android.launcher3.util.DisplayController
 import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
 import com.android.quickstep.util.LottieAnimationColorUtils
@@ -62,7 +61,7 @@
     LoggableTaskbarController {
 
     private val isTooltipEnabled: Boolean
-        get() = !Utilities.isRunningInTestHarness() && !isPhoneMode(activityContext.deviceProfile)
+        get() = !Utilities.isRunningInTestHarness() && !activityContext.isPhoneMode
     private val isOpen: Boolean
         get() = tooltip?.isOpen ?: false
     val isBeforeTooltipFeaturesStep: Boolean
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 057b71b..a850680 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -17,7 +17,6 @@
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
-import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
@@ -734,7 +733,7 @@
         }
         mIconAlphaForHome.setValue(alpha);
         boolean hotseatVisible = alpha == 0
-                || isPhoneMode(mLauncher.getDeviceProfile())
+                || mControllers.taskbarActivityContext.isPhoneMode()
                 || (!mControllers.uiController.isHotseatIconOnTopWhenAligned()
                 && mIconAlignment.value > 0);
         /*
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index bbac116..831bc11 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -492,23 +492,7 @@
         }
     }
 
-    /**
-     * @return {@code true} if provided device profile isn't a large screen profile
-     *                      and we are using a single window for taskbar and navbar.
-     */
-    public static boolean isPhoneMode(DeviceProfile deviceProfile) {
-        return ENABLE_TASKBAR_NAVBAR_UNIFICATION && deviceProfile.isPhone;
-    }
-
-    /**
-     * @return {@code true} if {@link #isPhoneMode(DeviceProfile)} is true and we're using
-     *                      3 button-nav
-     */
-    public static boolean isPhoneButtonNavMode(TaskbarActivityContext context) {
-        return isPhoneMode(context.getDeviceProfile()) && context.isThreeButtonNav();
-    }
-
-    private boolean isTaskbarPresent(DeviceProfile deviceProfile) {
+    private static boolean isTaskbarPresent(DeviceProfile deviceProfile) {
         return ENABLE_TASKBAR_NAVBAR_UNIFICATION || deviceProfile.isTaskbarPresent;
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 9aaa80f..b3a8b93 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -256,7 +256,7 @@
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
         mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
 
-        if (isPhoneMode()) {
+        if (mActivity.isPhoneMode()) {
             mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(
                     R.dimen.taskbar_phone_size);
             mStashedHeight = mActivity.getResources().getDimensionPixelSize(
@@ -316,7 +316,7 @@
         updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, isStashedInAppAuto);
         updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
         updateStateForFlag(FLAG_IN_SETUP, isInSetup);
-        updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, isPhoneMode()
+        updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, mActivity.isPhoneMode()
                 && !mActivity.isThreeButtonNav());
         // For now, assume we're in an app, since LauncherTaskbarUIController won't be able to tell
         // us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
@@ -386,13 +386,6 @@
         return (hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing());
     }
 
-    /**
-     * @return {@code true} if we're not on a large screen AND using gesture nav
-     */
-    private boolean isPhoneMode() {
-        return TaskbarManager.isPhoneMode(mActivity.getDeviceProfile());
-    }
-
     private boolean hasAnyFlag(int flagMask) {
         return hasAnyFlag(mState, flagMask);
     }
@@ -428,7 +421,7 @@
      * @see android.view.WindowInsets.Type#systemBars()
      */
     public int getContentHeightToReportToApps() {
-        if ((isPhoneMode() && !mActivity.isThreeButtonNav())
+        if ((mActivity.isPhoneMode() && !mActivity.isThreeButtonNav())
                 || DisplayController.isTransientTaskbar(mActivity)) {
             return getStashedHeight();
         }
@@ -579,7 +572,7 @@
         mAnimator = new AnimatorSet();
         addJankMonitorListener(mAnimator, /* appearing= */ !mIsStashed);
         boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
-        final float stashTranslation = isPhoneMode() || isTransientTaskbar
+        final float stashTranslation = mActivity.isPhoneMode() || isTransientTaskbar
                 ? 0
                 : (mUnstashedHeight - mStashedHeight);
 
@@ -651,7 +644,7 @@
 
             firstHalfAnimatorSet.playTogether(
                     mIconAlphaForStash.animateToValue(0),
-                    mIconScaleForStash.animateToValue(isPhoneMode() ?
+                    mIconScaleForStash.animateToValue(mActivity.isPhoneMode() ?
                             0 : STASHED_TASKBAR_SCALE)
             );
             secondHalfAnimatorSet.playTogether(
@@ -948,7 +941,7 @@
             return false;
         }
         return (mIsImeShowing || mIsImeSwitcherShowing) &&
-                !(isPhoneMode() && mActivity.isThreeButtonNav()
+                !(mActivity.isPhoneMode() && mActivity.isThreeButtonNav()
                         && mActivity.getDeviceProfile().isLandscape);
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 2ab0066..74517a8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -123,7 +123,7 @@
         mIconLayoutBounds = mActivityContext.getTransientTaskbarBounds();
         Resources resources = getResources();
         boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivityContext)
-                && !TaskbarManager.isPhoneMode(mActivityContext.getDeviceProfile());
+                && !mActivityContext.isPhoneMode();
         mIsRtl = Utilities.isRtl(resources);
         mTransientTaskbarMinWidth = resources.getDimension(R.dimen.transient_taskbar_min_width);
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index c0cbd45..33fb395 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -29,8 +29,6 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
-import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
-import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_PINNING_ANIM;
@@ -191,7 +189,7 @@
     public void init(TaskbarControllers controllers) {
         mControllers = controllers;
         mTaskbarView.init(new TaskbarViewCallbacks());
-        mTaskbarView.getLayoutParams().height = isPhoneMode(mActivity.getDeviceProfile())
+        mTaskbarView.getLayoutParams().height = mActivity.isPhoneMode()
                 ? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_phone_size)
                 : mActivity.getDeviceProfile().taskbarHeight;
 
@@ -219,7 +217,7 @@
             // This gets modified in NavbarButtonsViewController, but the initial value it reads
             // may be incorrect since it's state gets destroyed on taskbar recreate, so reset here
             mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN)
-                    .animateToValue(isPhoneButtonNavMode(mActivity) ? 0 : 1).start();
+                    .animateToValue(mActivity.isPhoneButtonNavMode() ? 0 : 1).start();
         }
         if (enableTaskbarPinning()) {
             mTaskbarView.addOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
@@ -598,7 +596,7 @@
      *                       1 => fully aligned
      */
     public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
-        if (isPhoneMode(launcherDp)) {
+        if (mActivity.isPhoneMode()) {
             mIconAlignControllerLazy = null;
             return;
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
index 3f51958..65b77ac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -24,8 +24,8 @@
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
-import com.android.launcher3.DeviceProfile
 import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
 import com.android.systemui.shared.rotation.RotationButton
 
@@ -48,7 +48,7 @@
             a11yButton
     ) {
 
-    override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
+    override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
         val iconSize: Int = resources.getDimensionPixelSize(DIMEN_TASKBAR_ICON_SIZE_KIDS)
         val buttonWidth: Int = resources.getDimensionPixelSize(DIMEN_TASKBAR_NAV_BUTTONS_WIDTH_KIDS)
         val buttonHeight: Int =
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 6b05e9a..22f0131 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -24,6 +24,7 @@
 import android.widget.ImageView
 import android.widget.LinearLayout
 import com.android.launcher3.DeviceProfile
+import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.launcher3.taskbar.navbutton.LayoutResourceHelper.*
 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.Companion
 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
@@ -162,6 +163,6 @@
 
     /** Lays out and provides access to the home, recents, and back buttons for various mischief */
     interface NavButtonLayoutter {
-        fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean)
+        fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean)
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
index 5a7bc49..3817f91 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
@@ -20,7 +20,7 @@
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.LinearLayout
-import com.android.launcher3.DeviceProfile
+import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.systemui.shared.rotation.RotationButton
 
 /** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */
@@ -43,7 +43,7 @@
                 a11yButton
         ) {
 
-    override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
+    override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
         endContextualContainer.removeAllViews()
         startContextualContainer.removeAllViews()
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 382e298..7583cc1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -23,9 +23,8 @@
 import android.widget.ImageView
 import android.widget.LinearLayout
 import androidx.core.view.children
-import com.android.launcher3.DeviceProfile
 import com.android.launcher3.R
-import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.launcher3.util.DimensionUtils
 import com.android.systemui.shared.rotation.RotationButton
 
@@ -48,11 +47,11 @@
             a11yButton
     ) {
 
-    override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
+    override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
         // TODO(b/230395757): Polish pending, this is just to make it usable
         val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
-        val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
-                TaskbarManager.isPhoneMode(dp))
+        val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(context.deviceProfile,
+                resources, context.isPhoneMode)
         navButtonContainer.removeAllViews()
         navButtonContainer.orientation = LinearLayout.VERTICAL
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index e1ffa4d..4388ce6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -22,9 +22,8 @@
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
-import com.android.launcher3.DeviceProfile
 import com.android.launcher3.R
-import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.launcher3.util.DimensionUtils
 import com.android.systemui.shared.rotation.RotationButton
 
@@ -47,11 +46,11 @@
             a11yButton
     ) {
 
-    override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
+    override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
         // TODO(b/230395757): Polish pending, this is just to make it usable
         val taskbarDimensions =
-            DimensionUtils.getTaskbarPhoneDimensions(dp, resources,
-                    TaskbarManager.isPhoneMode(dp))
+            DimensionUtils.getTaskbarPhoneDimensions(context.deviceProfile, resources,
+                    context.isPhoneMode)
         val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
 
         // Ensure order of buttons is correct
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index abdd32c..1ac0060 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -22,8 +22,8 @@
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
-import com.android.launcher3.DeviceProfile
 import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.systemui.shared.rotation.RotationButton
 
 class SetupNavLayoutter(
@@ -45,7 +45,7 @@
             a11yButton
     ) {
 
-    override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
+    override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
         // Since setup wizard only has back button enabled, it looks strange to be
         // end-aligned, so start-align instead.
         val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index f5a4c64..5465b6b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -22,8 +22,8 @@
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
-import com.android.launcher3.DeviceProfile
 import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.systemui.shared.rotation.RotationButton
 
 /**
@@ -48,9 +48,11 @@
             a11yButton
     ) {
 
-    override fun layoutButtons(dp: DeviceProfile, isA11yButtonPersistent: Boolean) {
+    override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
         // Add spacing after the end of the last nav button
-        var navMarginEnd = resources.getDimension(dp.inv.inlineNavButtonsEndSpacing).toInt()
+        var navMarginEnd = resources
+                .getDimension(context.deviceProfile.inv.inlineNavButtonsEndSpacing)
+                .toInt()
         val contextualWidth = endContextualContainer.width
         // If contextual buttons are showing, we check if the end margin is enough for the
         // contextual button to be showing - if not, move the nav buttons over a smidge
@@ -91,7 +93,7 @@
         endContextualContainer.removeAllViews()
         startContextualContainer.removeAllViews()
 
-        if (!dp.isGestureMode) {
+        if (!context.deviceProfile.isGestureMode) {
             val contextualMargin = resources.getDimensionPixelSize(
                     R.dimen.taskbar_contextual_button_padding)
             repositionContextualContainer(endContextualContainer, 0, Gravity.END)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 968faf0..6d3b60a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static android.view.MotionEvent.ACTION_DOWN;
+
 import static com.android.app.animation.Interpolators.ACCELERATE_0_75;
 import static com.android.app.animation.Interpolators.DECELERATE_3;
 import static com.android.app.animation.Interpolators.LINEAR;
@@ -65,6 +66,7 @@
 import android.view.MotionEvent;
 import android.view.animation.Interpolator;
 
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -190,8 +192,7 @@
     public void onDragStart(boolean start) {
         mMotionPauseDetector.clear();
         if (start) {
-            InteractionJankMonitorWrapper.begin(mRecentsView,
-                    InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+            InteractionJankMonitorWrapper.begin(mRecentsView, Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
 
             mStartState = mLauncher.getStateManager().getState();
 
@@ -327,7 +328,7 @@
         if (mMotionPauseDetector.isPaused() && noFling) {
             // Going to Overview.
             cancelAnimations();
-            InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+            InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
 
             StateAnimationConfig config = new StateAnimationConfig();
             config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
@@ -445,7 +446,7 @@
                     RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE,
                     RecentsView.SCROLL_VIBRATION_FALLBACK);
         } else {
-            InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+            InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
         }
 
         nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
@@ -469,7 +470,7 @@
                                         : LAUNCHER_UNKNOWN_SWIPEDOWN));
 
         if (targetState == QUICK_SWITCH_FROM_HOME) {
-            InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+            InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
         }
 
         mLauncher.getStateManager().goToState(targetState, false, forEndCallback(this::clearState));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 2c937b0..d94cd89 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -24,6 +24,7 @@
 import android.view.MotionEvent;
 
 import com.android.app.animation.Interpolators;
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
@@ -185,18 +186,15 @@
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN:
                 InteractionJankMonitorWrapper.begin(
-                        mLauncher.getRootView(), InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+                        mLauncher.getRootView(), Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS);
                 InteractionJankMonitorWrapper.begin(
-                        mLauncher.getRootView(),
-                        InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
+                        mLauncher.getRootView(), Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE);
                 break;
 
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
-                InteractionJankMonitorWrapper.cancel(
-                        InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
-                InteractionJankMonitorWrapper.cancel(
-                        InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
+                InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS);
+                InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE);
                 break;
         }
         return super.onControllerInterceptTouchEvent(ev);
@@ -207,11 +205,10 @@
     protected void onReinitToState(LauncherState newToState) {
         super.onReinitToState(newToState);
         if (newToState != ALL_APPS) {
-            InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+            InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS);
         }
         if (newToState != NORMAL) {
-            InteractionJankMonitorWrapper.cancel(
-                    InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
+            InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE);
         }
     }
 
@@ -219,18 +216,16 @@
     protected void onReachedFinalState(LauncherState toState) {
         super.onReachedFinalState(toState);
         if (toState == ALL_APPS) {
-            InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
+            InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS);
         } else if (toState == NORMAL) {
-            InteractionJankMonitorWrapper.end(
-                    InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
+            InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE);
         }
     }
 
     @Override
     protected void clearState() {
         super.clearState();
-        InteractionJankMonitorWrapper.cancel(InteractionJankMonitorWrapper.CUJ_OPEN_ALL_APPS);
-        InteractionJankMonitorWrapper.cancel(
-                InteractionJankMonitorWrapper.CUJ_CLOSE_ALL_APPS_SWIPE);
+        InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS);
+        InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index d7ff59e..42b18bd 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -96,6 +96,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
+import com.android.internal.jank.Cuj;
 import com.android.internal.util.LatencyTracker;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.DeviceProfile;
@@ -1024,12 +1025,12 @@
                     }
                     mHandled = true;
 
+                    InteractionJankMonitorWrapper.begin(mRecentsView, Cuj.CUJ_LAUNCHER_QUICK_SWITCH,
+                            2000 /* ms timeout */);
                     InteractionJankMonitorWrapper.begin(mRecentsView,
-                            InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH, 2000 /* ms timeout */);
+                            Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
                     InteractionJankMonitorWrapper.begin(mRecentsView,
-                            InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
-                    InteractionJankMonitorWrapper.begin(mRecentsView,
-                            InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
+                            Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
 
                     rv.post(() -> rv.getViewTreeObserver().removeOnDrawListener(this));
                 }
@@ -1145,16 +1146,13 @@
         View postResumeLastTask = mActivityInterface.onSettledOnEndTarget(endTarget);
 
         if (endTarget != NEW_TASK) {
-            InteractionJankMonitorWrapper.cancel(
-                    InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+            InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
         }
         if (endTarget != HOME) {
-            InteractionJankMonitorWrapper.cancel(
-                    InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
+            InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
         }
         if (endTarget != RECENTS) {
-            InteractionJankMonitorWrapper.cancel(
-                    InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
+            InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
         }
 
         switch (endTarget) {
diff --git a/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
new file mode 100644
index 0000000..645ecf4
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
@@ -0,0 +1,136 @@
+package com.android.quickstep
+
+import android.app.backup.BackupManager
+import android.app.backup.BackupRestoreEventLogger
+import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType
+import android.app.backup.BackupRestoreEventLogger.BackupRestoreError
+import android.content.Context
+import com.android.launcher3.Flags
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger
+
+/**
+ * Concrete implementation for wrapper to log Restore event metrics for both success and failure to
+ * restore Launcher workspace from a backup. This implementation accesses SystemApis so is only
+ * available to QuickStep/NexusLauncher.
+ */
+class LauncherRestoreEventLoggerImpl(val context: Context) : LauncherRestoreEventLogger() {
+    companion object {
+        const val TAG = "LauncherRestoreEventLoggerImpl"
+
+        // Generic type for any possible workspace items, when specific type is not known.
+        @BackupRestoreDataType private const val DATA_TYPE_LAUNCHER_ITEM = "launcher_item"
+        // Specific workspace item types, based off of Favorites Table.
+        @BackupRestoreDataType private const val DATA_TYPE_APPLICATION = "application"
+        @BackupRestoreDataType private const val DATA_TYPE_FOLDER = "folder"
+        @BackupRestoreDataType private const val DATA_TYPE_APPWIDGET = "widget"
+        @BackupRestoreDataType private const val DATA_TYPE_CUSTOM_APPWIDGET = "custom_widget"
+        @BackupRestoreDataType private const val DATA_TYPE_DEEP_SHORTCUT = "deep_shortcut"
+        @BackupRestoreDataType private const val DATA_TYPE_APP_PAIR = "app_pair"
+    }
+
+    private val backupManager: BackupManager = BackupManager(context)
+    private val restoreEventLogger: BackupRestoreEventLogger = backupManager.delayedRestoreLogger
+
+    /**
+     * For logging when multiple items of a given data type failed to restore.
+     *
+     * @param dataType The data type that was not restored.
+     * @param count the number of data items that were not restored.
+     * @param error error type for why the data was not restored.
+     */
+    override fun logLauncherItemsRestoreFailed(
+        @BackupRestoreDataType dataType: String,
+        count: Int,
+        @BackupRestoreError error: String?
+    ) {
+        if (Flags.enableLauncherBrMetrics()) {
+            restoreEventLogger.logItemsRestoreFailed(dataType, count, error)
+        }
+    }
+
+    /**
+     * For logging when multiple items of a given data type were successfully restored.
+     *
+     * @param dataType The data type that was restored.
+     * @param count the number of data items restored.
+     */
+    override fun logLauncherItemsRestored(@BackupRestoreDataType dataType: String, count: Int) {
+        if (Flags.enableLauncherBrMetrics()) {
+            restoreEventLogger.logItemsRestored(dataType, count)
+        }
+    }
+
+    /**
+     * Helper to log successfully restoring a single item from the Favorites table.
+     *
+     * @param favoritesId The id of the item type from [Favorites] that was restored.
+     */
+    override fun logSingleFavoritesItemRestored(favoritesId: Int) {
+        if (Flags.enableLauncherBrMetrics()) {
+            restoreEventLogger.logItemsRestored(favoritesIdToDataType(favoritesId), 1)
+        }
+    }
+
+    /**
+     * Helper to log a failure to restore a single item from the Favorites table.
+     *
+     * @param favoritesId The id of the item type from [Favorites] that was not restored.
+     * @param error error type for why the data was not restored.
+     */
+    override fun logSingleFavoritesItemRestoreFailed(
+        favoritesId: Int,
+        @BackupRestoreError error: String?
+    ) {
+        if (Flags.enableLauncherBrMetrics()) {
+            restoreEventLogger.logItemsRestoreFailed(favoritesIdToDataType(favoritesId), 1, error)
+        }
+    }
+
+    /**
+     * Helper to log a failure to restore items from the Favorites table.
+     *
+     * @param favoritesId The id of the item type from [Favorites] that was not restored.
+     * @param count number of items that failed to restore.
+     * @param error error type for why the data was not restored.
+     */
+    override fun logFavoritesItemsRestoreFailed(
+        favoritesId: Int,
+        count: Int,
+        @BackupRestoreError error: String?
+    ) {
+        if (Flags.enableLauncherBrMetrics()) {
+            restoreEventLogger.logItemsRestoreFailed(
+                favoritesIdToDataType(favoritesId),
+                count,
+                error
+            )
+        }
+    }
+
+    /**
+     * Uses the current [restoreEventLogger] to report its results to the [backupManager]. Use when
+     * done restoring items for Launcher.
+     */
+    override fun reportLauncherRestoreResults() {
+        if (Flags.enableLauncherBrMetrics()) {
+            backupManager.reportDelayedRestoreResult(restoreEventLogger)
+        }
+    }
+
+    /**
+     * Helper method to convert item types from [Favorites] to B&R data types for logging. Also to
+     * avoid direct usage of @BackupRestoreDataType which is protected under @SystemApi.
+     */
+    @BackupRestoreDataType
+    private fun favoritesIdToDataType(favoritesId: Int): String =
+        when (favoritesId) {
+            Favorites.ITEM_TYPE_APPLICATION -> DATA_TYPE_APPLICATION
+            Favorites.ITEM_TYPE_FOLDER -> DATA_TYPE_FOLDER
+            Favorites.ITEM_TYPE_APPWIDGET -> DATA_TYPE_APPWIDGET
+            Favorites.ITEM_TYPE_CUSTOM_APPWIDGET -> DATA_TYPE_CUSTOM_APPWIDGET
+            Favorites.ITEM_TYPE_DEEP_SHORTCUT -> DATA_TYPE_DEEP_SHORTCUT
+            Favorites.ITEM_TYPE_APP_PAIR -> DATA_TYPE_APP_PAIR
+            else -> DATA_TYPE_LAUNCHER_ITEM
+        }
+}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 31fe791..b2429ad 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -31,6 +31,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StatefulActivity;
@@ -262,7 +263,7 @@
         if (activity != null) {
             InteractionJankMonitorWrapper.begin(
                     activity.getRootView(),
-                    InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+                    Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
         }
 
         GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE,
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 341e18c..06a442b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -25,14 +25,13 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.IRecentsAnimationController;
-import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.WindowManagerGlobal;
 import android.window.PictureInPictureSurfaceTransaction;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 
+import com.android.internal.jank.Cuj;
 import com.android.internal.os.IResultReceiver;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RunnableList;
@@ -183,10 +182,9 @@
                     });
                 }
             });
-            InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
-            InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
-            InteractionJankMonitorWrapper.end(
-                    InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
+            InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
+            InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
+            InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
         };
         if (forceFinish) {
             finishCb.run();
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 11c5ab4..4e84f4a 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -60,6 +60,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.app.animation.Interpolators;
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimatedFloat;
@@ -395,8 +396,7 @@
             @Override
             public void onAnimationSuccess(Animator animator) {
                 if (isQuickSwitch) {
-                    InteractionJankMonitorWrapper.end(
-                            InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
+                    InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
                 }
             }
 
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index bd4625b..d1d2f97 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -974,8 +974,8 @@
             TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext();
             if (tac != null && !(base instanceof AssistantInputConsumer)) {
                 // Present always on large screen or on small screen w/ flag
-                DeviceProfile dp = tac.getDeviceProfile();
-                boolean useTaskbarConsumer = dp.isTaskbarPresent && !TaskbarManager.isPhoneMode(dp)
+                boolean useTaskbarConsumer = tac.getDeviceProfile().isTaskbarPresent
+                        && !tac.isPhoneMode()
                         && !tac.isInStashedLauncherState();
                 if (canStartSystemGesture && useTaskbarConsumer) {
                     reasonString.append(NEWLINE_PREFIX)
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index 69c15a5..c91ee81 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -25,6 +25,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.app.Activity;
+import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.graphics.Insets;
@@ -86,6 +87,8 @@
     private boolean mIsFoldable;
     private boolean mOnAttachedToWindowPendingCreate;
 
+    @Nullable private Runnable mOnAttachedOnGlobalLayoutCallback = null;
+
     public static TutorialFragment newInstance(
             TutorialType tutorialType, boolean gestureComplete, boolean fromTutorialMenu) {
         TutorialFragment fragment = getFragmentForTutorialType(tutorialType, fromTutorialMenu);
@@ -349,13 +352,27 @@
                     new ViewTreeObserver.OnGlobalLayoutListener() {
                         @Override
                         public void onGlobalLayout() {
-                            changeController(mTutorialType);
+                            runOnAttached(() -> changeController(mTutorialType));
                             mRootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                         }
                     });
         }
     }
 
+    private void runOnAttached(Runnable callback) {
+        mOnAttachedOnGlobalLayoutCallback = callback;
+        if (getContext() != null) {
+            onAttached();
+        }
+    }
+
+    private void onAttached() {
+        if (mOnAttachedOnGlobalLayoutCallback != null) {
+            mOnAttachedOnGlobalLayoutCallback.run();
+            mOnAttachedOnGlobalLayoutCallback = null;
+        }
+    }
+
     @Override
     public boolean onTouch(View view, MotionEvent motionEvent) {
         if (mTutorialController != null && !isGestureComplete()) {
@@ -378,6 +395,12 @@
     }
 
     @Override
+    public void onAttach(@NonNull Context context) {
+        super.onAttach(context);
+        onAttached();
+    }
+
+    @Override
     void onAttachedToWindow() {
         if (mEdgeBackGestureHandler == null) {
             mOnAttachedToWindowPendingCreate = true;
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index bce2e82..cf9fc74 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -42,6 +42,7 @@
 import androidx.annotation.WorkerThread;
 import androidx.slice.SliceItem;
 
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logger.LauncherAtom;
@@ -401,11 +402,10 @@
                 case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN:
                     InteractionJankMonitorWrapper.begin(
                             view,
-                            InteractionJankMonitorWrapper.CUJ_ALL_APPS_SCROLL);
+                            Cuj.CUJ_LAUNCHER_ALL_APPS_SCROLL);
                     break;
                 case LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END:
-                    InteractionJankMonitorWrapper.end(
-                            InteractionJankMonitorWrapper.CUJ_ALL_APPS_SCROLL);
+                    InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_ALL_APPS_SCROLL);
                     break;
                 default:
                     break;
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index ade8074..b2d1b43 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -23,6 +23,7 @@
 import android.animation.ObjectAnimator
 import android.animation.ValueAnimator
 import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.graphics.Bitmap
 import android.graphics.Rect
 import android.graphics.RectF
@@ -31,9 +32,11 @@
 import android.view.SurfaceControl
 import android.view.SurfaceControl.Transaction
 import android.view.View
-import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_FRONT
 import android.window.TransitionInfo
 import android.window.TransitionInfo.Change
+import android.window.WindowContainerToken
 import androidx.annotation.VisibleForTesting
 import com.android.app.animation.Interpolators
 import com.android.launcher3.DeviceProfile
@@ -387,14 +390,7 @@
                 "trying to launch an app pair icon, but encountered an unexpected null"
             }
 
-            composeIconSplitLaunchAnimator(
-                launchingIconView,
-                initialTaskId,
-                secondTaskId,
-                info,
-                t,
-                finishCallback
-            )
+            composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
         } else {
             // Fallback case: simple fade-in animation
             check(info != null && t != null) {
@@ -461,12 +457,27 @@
     /**
      * When the user taps an app pair icon to launch split, this will play the tasks' launch
      * animation from the position of the icon.
+     *
+     * To find the root shell leash that we want to fade in, we do the following:
+     * The Changes we receive in transitionInfo are structured like this
+     *
+     *     Root (grandparent)
+     *     |
+     *     |--> Split Root 1 (left/top side parent) (WINDOWING_MODE_MULTI_WINDOW)
+     *     |   |
+     *     |    --> App 1 (left/top side child) (WINDOWING_MODE_MULTI_WINDOW)
+     *     |--> Divider
+     *     |--> Split Root 2 (right/bottom side parent) (WINDOWING_MODE_MULTI_WINDOW)
+     *         |
+     *          --> App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW)
+     *
+     * We want to animate the Root (grandparent) so that it affects both apps and the divider.
+     * To do this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the
+     * left-side ones, for simplicity) and traverse the tree until we find the grandparent.
      */
     @VisibleForTesting
     fun composeIconSplitLaunchAnimator(
         launchingIconView: AppPairIcon,
-        initialTaskId: Int,
-        secondTaskId: Int,
         transitionInfo: TransitionInfo,
         t: Transaction,
         finishCallback: Runnable
@@ -481,46 +492,47 @@
         progressUpdater.setDuration(timings.getDuration().toLong())
         progressUpdater.interpolator = Interpolators.LINEAR
 
-        // Find the root shell leash that we want to fade in (parent of both app windows and
-        // the divider). For simplicity, we search using the initialTaskId.
-        var rootShellLayer: SurfaceControl? = null
-        var dividerPos = 0
+        var rootCandidate: Change? = null
 
         for (change in transitionInfo.changes) {
             val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
-            val taskId = taskInfo.taskId
-            val mode = change.mode
 
-            if (taskId == initialTaskId || taskId == secondTaskId) {
-                check(
-                    mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT
-                ) {
-                    "Expected task to be showing, but it is $mode"
+            // TODO (b/316490565): Replace this logic when SplitBounds is available to
+            //  startAnimation() and we can know the precise taskIds of launching tasks.
+            // Find a change that has WINDOWING_MODE_MULTI_WINDOW.
+            if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW &&
+                (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)) {
+                // Check if it is a left/top app.
+                val isLeftTopApp =
+                    (dp.isLeftRightSplit && change.endAbsBounds.left == 0) ||
+                        (!dp.isLeftRightSplit && change.endAbsBounds.top == 0)
+                if (isLeftTopApp) {
+                    // Found one!
+                    rootCandidate = change
+                    break
                 }
             }
-
-            if (taskId == initialTaskId) {
-                var splitRoot1 = change
-                val parentToken = change.parent
-                if (parentToken != null) {
-                    splitRoot1 = transitionInfo.getChange(parentToken) ?: change
-                }
-
-                val topLevelToken = splitRoot1.parent
-                if (topLevelToken != null) {
-                    rootShellLayer = transitionInfo.getChange(topLevelToken)?.leash
-                }
-
-                dividerPos =
-                    if (dp.isLeftRightSplit) change.endAbsBounds.right
-                    else change.endAbsBounds.bottom
-            }
         }
 
-        check(rootShellLayer != null) {
-            "Could not find a TransitionInfo.Change matching the initialTaskId"
+        // If we could not find a proper root candidate, something went wrong.
+        check(rootCandidate != null) { "Could not find a split root candidate" }
+
+        // Find the place where our left/top app window meets the divider (used for the
+        // launcher side animation)
+        val dividerPos =
+            if (dp.isLeftRightSplit) rootCandidate.endAbsBounds.right
+            else rootCandidate.endAbsBounds.bottom
+
+        // Recurse up the tree until parent is null, then we've found our root.
+        var parentToken: WindowContainerToken? = rootCandidate.parent
+        while (parentToken != null) {
+            rootCandidate = transitionInfo.getChange(parentToken) ?: break
+            parentToken = rootCandidate.parent
         }
 
+        // Make sure nothing weird happened, like getChange() returning null.
+        check(rootCandidate != null) { "Failed to find a root leash" }
+
         // Shell animation: the apps are revealed toward end of the launch animation
         progressUpdater.addUpdateListener { valueAnimator: ValueAnimator ->
             val progress =
@@ -532,7 +544,7 @@
                 )
 
             // Set the alpha of the shell layer (2 apps + divider)
-            t.setAlpha(rootShellLayer, progress)
+            t.setAlpha(rootCandidate.leash, progress)
             t.apply()
         }
 
@@ -651,9 +663,7 @@
             // Find the target tasks' root tasks since those are the split stages that need to
             // be animated (the tasks themselves are children and thus inherit animation).
             if (taskId == initialTaskId || taskId == secondTaskId) {
-                check(
-                    mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT
-                ) {
+                check(mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
                     "Expected task to be showing, but it is $mode"
                 }
             }
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index e705285..28efc97 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -34,6 +34,7 @@
 import android.os.UserHandle;
 import android.view.View;
 
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
@@ -174,8 +175,7 @@
             public void onAnimationEnd(Animator animation) {
                 if (!mIsCancelled) {
                     mController.launchSplitTasks(aBoolean -> cleanUp());
-                    InteractionJankMonitorWrapper.end(
-                            InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+                    InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
                 }
             }
 
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index fe6ce46..a36b32c 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -16,8 +16,6 @@
 
 package com.android.quickstep.util;
 
-import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_PIP;
-
 import android.animation.Animator;
 import android.animation.RectEvaluator;
 import android.content.ComponentName;
@@ -35,6 +33,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.icons.IconProvider;
 import com.android.quickstep.TaskAnimationManager;
@@ -174,19 +173,19 @@
         addAnimatorListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationStart(Animator animation) {
-                InteractionJankMonitorWrapper.begin(view, CUJ_APP_CLOSE_TO_PIP);
+                InteractionJankMonitorWrapper.begin(view, Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
                 super.onAnimationStart(animation);
             }
 
             @Override
             public void onAnimationCancel(Animator animation) {
                 super.onAnimationCancel(animation);
-                InteractionJankMonitorWrapper.cancel(CUJ_APP_CLOSE_TO_PIP);
+                InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
             }
 
             @Override
             public void onAnimationSuccess(Animator animator) {
-                InteractionJankMonitorWrapper.end(CUJ_APP_CLOSE_TO_PIP);
+                InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
             }
 
             @Override
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 5d8e53e..baaa062 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -427,9 +427,16 @@
             // conflict with layers that WM core positions (ie. the input consumers).  For shell
             // transitions, the animation leashes are reparented to an animation container so we
             // can bump layers as needed.
-            builder.setLayer(mDrawsBelowRecents
-                    ? Integer.MIN_VALUE + app.prefixOrderIndex
-                    : ENABLE_SHELL_TRANSITIONS ? Integer.MAX_VALUE : 0);
+            if (ENABLE_SHELL_TRANSITIONS) {
+                builder.setLayer(mDrawsBelowRecents
+                        ? Integer.MIN_VALUE + app.prefixOrderIndex
+                        // 1000 is an arbitrary number to give room for multiple layers.
+                        : Integer.MAX_VALUE - 1000 + app.prefixOrderIndex);
+            } else {
+                builder.setLayer(mDrawsBelowRecents
+                        ? Integer.MIN_VALUE + app.prefixOrderIndex
+                        : 0);
+            }
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/unfold/LauncherJankMonitorTransitionProgressListener.kt b/quickstep/src/com/android/quickstep/util/unfold/LauncherJankMonitorTransitionProgressListener.kt
index 4f89c7e..b4ca35e 100644
--- a/quickstep/src/com/android/quickstep/util/unfold/LauncherJankMonitorTransitionProgressListener.kt
+++ b/quickstep/src/com/android/quickstep/util/unfold/LauncherJankMonitorTransitionProgressListener.kt
@@ -16,6 +16,7 @@
 package com.android.quickstep.util.unfold
 
 import android.view.View
+import com.android.internal.jank.Cuj
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import java.util.function.Supplier
@@ -28,11 +29,11 @@
     override fun onTransitionStarted() {
         InteractionJankMonitorWrapper.begin(
             attachedViewProvider.get(),
-            InteractionJankMonitorWrapper.CUJ_LAUNCHER_UNFOLD_ANIM
+            Cuj.CUJ_LAUNCHER_UNFOLD_ANIM
         )
     }
 
     override fun onTransitionFinished() {
-        InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_LAUNCHER_UNFOLD_ANIM)
+        InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_UNFOLD_ANIM)
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index dd201af..2ae64ff 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -17,6 +17,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -246,12 +247,11 @@
         RunnableList endCallback = new RunnableList();
         RecentsView recentsView = getRecentsView();
         // Callbacks run from remote animation when recents animation not currently running
-        InteractionJankMonitorWrapper.begin(this,
-                InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Enter form GroupedTaskView");
+        InteractionJankMonitorWrapper.begin(this, Cuj.CUJ_SPLIT_SCREEN_ENTER,
+                "Enter form GroupedTaskView");
         launchTaskInternal(success -> {
             endCallback.executeAllAndDestroy();
-            InteractionJankMonitorWrapper.end(
-                    InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+            InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
         }, false /* freezeTaskList */, true /*launchingExistingTaskview*/);
 
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 87cee63..ca9d13e 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -133,6 +133,7 @@
 import androidx.annotation.UiThread;
 import androidx.core.graphics.ColorUtils;
 
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener;
 import com.android.launcher3.DeviceProfile;
@@ -1444,8 +1445,7 @@
             mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true);
         }
         if (mOverviewStateEnabled) { // only when in overview
-            InteractionJankMonitorWrapper.begin(/* view= */ this,
-                    InteractionJankMonitorWrapper.CUJ_RECENTS_SCROLLING);
+            InteractionJankMonitorWrapper.begin(/* view= */ this, Cuj.CUJ_RECENTS_SCROLLING);
         }
     }
 
@@ -1460,7 +1460,7 @@
         if (getNextPage() > 0) {
             setSwipeDownShouldLaunchApp(true);
         }
-        InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_RECENTS_SCROLLING);
+        InteractionJankMonitorWrapper.end(Cuj.CUJ_RECENTS_SCROLLING);
     }
 
     @Override
@@ -3313,8 +3313,8 @@
                         timings.getInstructionsUnfoldEndOffset()));
         mSplitSelectStateController.setSplitInstructionsView(splitInstructionsView);
 
-        InteractionJankMonitorWrapper.begin(this,
-                InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "First tile selected");
+        InteractionJankMonitorWrapper.begin(this, Cuj.CUJ_SPLIT_SCREEN_ENTER,
+                "First tile selected");
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
@@ -3330,8 +3330,7 @@
         });
         anim.addEndListener(success -> {
             if (success) {
-                InteractionJankMonitorWrapper.end(
-                        InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+                InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
             } else {
                 // If transition to split select was interrupted, clean up to prevent glitches
                 if (FeatureFlags.enableSplitContextually()) {
@@ -3339,8 +3338,7 @@
                 } else {
                     resetFromSplitSelectionState();
                 }
-                InteractionJankMonitorWrapper.cancel(
-                        InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+                InteractionJankMonitorWrapper.cancel(Cuj.CUJ_SPLIT_SCREEN_ENTER);
             }
 
             updateCurrentTaskActionsVisibility();
@@ -4836,8 +4834,7 @@
                         } else {
                             resetFromSplitSelectionState();
                         }
-                        InteractionJankMonitorWrapper.end(
-                                InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
+                        InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
                     });
         });
 
@@ -4847,8 +4844,8 @@
                     mSplitSelectStateController.getSecondTaskId());
         }
 
-        InteractionJankMonitorWrapper.begin(this,
-                InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "Second tile selected");
+        InteractionJankMonitorWrapper.begin(this, Cuj.CUJ_SPLIT_SCREEN_ENTER,
+                "Second tile selected");
 
         // Fade out all other views underneath placeholders
         ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index b42f055..66a880b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -1035,9 +1035,6 @@
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationStart(Animator animation) {
-                    if (!recentsView.showAsGrid()) {
-                        return;
-                    }
                     recentsView.runActionOnRemoteHandles(
                             (Consumer<RemoteTargetHandle>) remoteTargetHandle ->
                                     remoteTargetHandle
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 8d54dce..39633ea 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -218,10 +218,17 @@
     }
 
     private BaseOverview pressHomeAndGoToOverview() {
-        mDevice.pressHome();
+        pressHomeAndWaitForOverviewClose();
         return mLauncher.getLaunchedAppState().switchToOverview();
     }
 
+    private void pressHomeAndWaitForOverviewClose() {
+        mDevice.pressHome();
+        Wait.atMost("Recents activity didn't stop",
+                () -> getFromRecents(recents -> !recents.isStarted()),
+                DEFAULT_UI_TIMEOUT, mLauncher);
+    }
+
     // b/143488140
     //@NavigationModeSwitch
     @Test
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 86018b1..929bd8e 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -249,7 +249,7 @@
         val spySplitAnimationController = spy(splitAnimationController)
         doNothing()
             .whenever(spySplitAnimationController)
-            .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any(), any())
+            .composeIconSplitLaunchAnimator(any(), any(), any(), any())
 
         spySplitAnimationController.playSplitLaunchAnimation(
             null /* launchingTaskView */,
@@ -267,7 +267,7 @@
         )
 
         verify(spySplitAnimationController)
-            .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any(), any())
+            .composeIconSplitLaunchAnimator(any(), any(), any(), any())
     }
 
     @Test
diff --git a/res/layout/folder_icon.xml b/res/layout/folder_icon.xml
index 4093744..6af346e 100644
--- a/res/layout/folder_icon.xml
+++ b/res/layout/folder_icon.xml
@@ -19,7 +19,8 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
-    android:focusable="true" >
+    android:focusable="true"
+    android:defaultFocusHighlightEnabled="false">
     <com.android.launcher3.views.DoubleShadowBubbleTextView
         style="@style/BaseIcon.Workspace"
         android:id="@+id/folder_icon_name"
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 9d5a55f..415d1f3 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -173,10 +173,10 @@
     <string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Genoptag"</string>
     <string name="developer_options_filter_hint" msgid="5896817443635989056">"Filter"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Mislykket: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
-    <string name="private_space_label" msgid="2359721649407947001">"Privat rum"</string>
+    <string name="private_space_label" msgid="2359721649407947001">"Privat område"</string>
     <string name="ps_container_title" msgid="4391796149519594205">"Privat"</string>
     <string name="ps_container_settings" msgid="6059734123353320479">"Indstillinger for privat rum"</string>
-    <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Lås/oplås det private rum"</string>
-    <string name="ps_container_transition" msgid="8667331812048014412">"Ændringer af tilstanden for det private rum"</string>
+    <string name="ps_container_lock_unlock_button" msgid="7605602332253423755">"Lås/oplås det private område"</string>
+    <string name="ps_container_transition" msgid="8667331812048014412">"Ændringer af tilstanden for det private område"</string>
     <string name="bubble_bar_overflow_description" msgid="7410995531938041192">"Overløb"</string>
 </resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 6c3b54c..53d7d0c 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -25,6 +25,7 @@
     <color name="uninstall_target_hover_tint">#FFF0592B</color>
 
     <color name="focused_background">#80c6c5c5</color>
+    <color name="focus_outline_color">@color/material_color_on_secondary_container</color>
 
     <color name="default_shadow_color_no_alpha">#FF000000</color>
 
diff --git a/res/values/config.xml b/res/values/config.xml
index 154312a..2980635 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -86,6 +86,7 @@
     <string name="widget_holder_factory_class" translatable="false"></string>
     <string name="taskbar_search_session_controller_class" translatable="false"></string>
     <string name="taskbar_model_callbacks_factory_class" translatable="false"></string>
+    <string name="launcher_restore_event_logger_class" translatable="false"></string>
 
     <!-- View ID to use for QSB widget -->
     <item type="id" name="qsb_widget" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 0a57127..62ce256 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -436,6 +436,9 @@
     <dimen name="split_instructions_bottom_margin_phone_portrait">60dp</dimen>
     <dimen name="split_instructions_start_margin_cancel">8dp</dimen>
 
+    <dimen name="focus_outline_radius">16dp</dimen>
+    <dimen name="focus_outline_stroke_width">3dp</dimen>
+
     <!-- Workspace grid visualization parameters -->
     <dimen name="grid_visualization_rounding_radius">16dp</dimen>
     <dimen name="grid_visualization_horizontal_cell_spacing">6dp</dimen>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index e2e528c..91da7e6 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -175,6 +175,7 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private DotInfo mDotInfo;
     private DotRenderer mDotRenderer;
+    private Locale mCurrentLocale;
     @ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
     protected DotRenderer.DrawParams mDotParams;
     private Animator mDotScaleAnim;
@@ -250,6 +251,7 @@
 
         mDotParams = new DotRenderer.DrawParams();
 
+        mCurrentLocale = context.getResources().getConfiguration().locale;
         setEllipsize(TruncateAt.END);
         setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
         setTextAlpha(1f);
@@ -411,10 +413,12 @@
      *  Only if actual text can be displayed in two line, the {@code true} value will be effective.
      */
     protected boolean shouldUseTwoLine() {
-        return ((FeatureFlags.enableTwolineAllapps())
-                && (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW))
-                || (FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()
-                && mDisplay == DISPLAY_SEARCH_RESULT);
+        return (FeatureFlags.enableTwolineAllapps() && isCurrentLanguageEnglish())
+                && (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW);
+    }
+
+    protected boolean isCurrentLanguageEnglish() {
+        return mCurrentLocale.equals(Locale.US);
     }
 
     @UiThread
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index b6e8ec3..1ca7da9 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -356,7 +356,7 @@
         final Resources res = context.getResources();
         mMetrics = res.getDisplayMetrics();
 
-        mIconSizeSteps = mIsResponsiveGrid ? new IconSizeSteps(res) : null;
+        mIconSizeSteps = new IconSizeSteps(res);
 
         // Determine sizes.
         widthPx = windowBounds.bounds.width();
@@ -1486,6 +1486,17 @@
                 folderCellWidthPx = roundPxValueFromFloat(folderCellWidthPx * scale);
                 folderCellHeightPx = roundPxValueFromFloat(folderCellHeightPx * scale);
             }
+            // Recalculating padding and cell height
+            folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
+
+            CellContentDimensions cellContentDimensions = new CellContentDimensions(
+                    folderChildIconSizePx,
+                    folderChildDrawablePaddingPx,
+                    folderChildTextSizePx);
+            cellContentDimensions.resizeToFitCellHeight(folderCellHeightPx, mIconSizeSteps);
+            folderChildIconSizePx = cellContentDimensions.getIconSizePx();
+            folderChildDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx();
+            folderChildTextSizePx = cellContentDimensions.getIconTextSizePx();
 
             folderContentPaddingTop = roundPxValueFromFloat(folderContentPaddingTop * scale);
             folderCellLayoutBorderSpacePx = new Point(
@@ -1493,10 +1504,7 @@
                     roundPxValueFromFloat(folderCellLayoutBorderSpacePx.y * scale)
             );
             folderFooterHeightPx = roundPxValueFromFloat(folderFooterHeightPx * scale);
-
             folderContentPaddingLeftRight = folderCellLayoutBorderSpacePx.x;
-
-            folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
         } else {
             int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding)
                     * scale);
diff --git a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
new file mode 100644
index 0000000..16b1854
--- /dev/null
+++ b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
@@ -0,0 +1,84 @@
+package com.android.launcher3.backuprestore
+
+import android.content.Context
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.R
+import com.android.launcher3.util.ResourceBasedOverride
+
+/**
+ * Wrapper for logging Restore event metrics for both success and failure to restore the Launcher
+ * workspace from a backup.
+ */
+open class LauncherRestoreEventLogger : ResourceBasedOverride {
+
+    companion object {
+        const val TAG = "LauncherRestoreEventLogger"
+
+        fun newInstance(context: Context?): LauncherRestoreEventLogger {
+            return ResourceBasedOverride.Overrides.getObject(
+                LauncherRestoreEventLogger::class.java,
+                context,
+                R.string.launcher_restore_event_logger_class
+            )
+        }
+    }
+
+    /**
+     * For logging when multiple items of a given data type failed to restore.
+     *
+     * @param dataType The data type that was not restored.
+     * @param count the number of data items that were not restored.
+     * @param error error type for why the data was not restored.
+     */
+    open fun logLauncherItemsRestoreFailed(dataType: String, count: Int, error: String?) {
+        // no-op
+    }
+
+    /**
+     * For logging when multiple items of a given data type were successfully restored.
+     *
+     * @param dataType The data type that was restored.
+     * @param count the number of data items restored.
+     */
+    open fun logLauncherItemsRestored(dataType: String, count: Int) {
+        // no-op
+    }
+
+    /**
+     * Helper to log successfully restoring a single item from the Favorites table.
+     *
+     * @param favoritesId The id of the item type from [Favorites] that was restored.
+     */
+    open fun logSingleFavoritesItemRestored(favoritesId: Int) {
+        // no-op
+    }
+
+    /**
+     * Helper to log a failure to restore a single item from the Favorites table.
+     *
+     * @param favoritesId The id of the item type from [Favorites] that was not restored.
+     * @param error error type for why the data was not restored.
+     */
+    open fun logSingleFavoritesItemRestoreFailed(favoritesId: Int, error: String?) {
+        // no-op
+    }
+
+    /**
+     * Helper to log a failure to restore items from the Favorites table.
+     *
+     * @param favoritesId The id of the item type from [Favorites] that was not restored.
+     * @param count number of items that failed to restore.
+     * @param error error type for why the data was not restored.
+     */
+    open fun logFavoritesItemsRestoreFailed(favoritesId: Int, count: Int, error: String?) {
+        // no-op
+    }
+
+    /**
+     * Uses the current [restoreEventLogger] to report its results to the [backupManager]. Use when
+     * done restoring items for Launcher.
+     */
+    open fun reportLauncherRestoreResults() {
+        // no-op
+    }
+}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 2f62840..7dfce56 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -201,10 +201,6 @@
             "ENABLE_PARAMETRIZE_REORDER", DISABLED,
             "Enables generating the reorder using a set of parameters");
 
-    public static final BooleanFlag ENABLE_NO_LONG_PRESS_DRAG = getDebugFlag(299748096,
-            "ENABLE_NO_LONG_PRESS_DRAG", ENABLED,
-            "Don't trigger the drag if we are still under long press");
-
     // TODO(Block 12): Clean up flags
     public static final BooleanFlag ENABLE_MULTI_INSTANCE = getDebugFlag(270396680,
             "ENABLE_MULTI_INSTANCE", DISABLED,
@@ -308,7 +304,7 @@
                     "Long press of nav handle is instantly triggered if deep press is detected.");
 
     public static final IntFlag LPNH_HAPTIC_HINT_DELAY =
-            getIntFlag(309972570, "LPNH_HAPTIC_HINT_ITERATIONS", 0,
+            getIntFlag(309972570, "LPNH_HAPTIC_HINT_DELAY", 0,
                     "Delay before haptic hint starts.");
 
     // TODO(Block 17): Clean up flags
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 777f4d5..aa5329b 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.dragndrop;
 
 import static com.android.launcher3.Utilities.ATLEAST_Q;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_NO_LONG_PRESS_DRAG;
 
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -464,7 +463,7 @@
 
     private DropTarget checkTouchMove(final int x, final int y) {
         // If we are in predrag, don't trigger any other event until we get out of it
-        if (ENABLE_NO_LONG_PRESS_DRAG.get() && mIsInPreDrag) {
+        if (mIsInPreDrag) {
             return mLastDropTarget;
         }
         DropTarget dropTarget = findDropTarget(x, y);
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 3330448..7dcc8a8 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -35,6 +35,7 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -197,7 +198,7 @@
         mUiHandler = new Handler(Looper.getMainLooper());
         mContext = context;
         mIdp = idp;
-        mDp = idp.getDeviceProfile(context).toBuilder(context).setViewScaleProvider(
+        mDp = getDeviceProfileForPreview(context).toBuilder(context).setViewScaleProvider(
                 this::getAppWidgetScale).build();
         if (context instanceof PreviewContext) {
             Context tempContext = ((PreviewContext) context).getBaseContext();
@@ -260,6 +261,21 @@
     }
 
     /**
+     * Returns the device profile based on resource configuration for previewing various display
+     * sizes
+     */
+    private DeviceProfile getDeviceProfileForPreview(Context context) {
+        float density = context.getResources().getDisplayMetrics().density;
+        Configuration config = context.getResources().getConfiguration();
+
+        return mIdp.getBestMatch(
+                config.screenWidthDp * density,
+                config.screenHeightDp * density,
+                WindowManagerProxy.INSTANCE.get(context).getRotation(context)
+        );
+    }
+
+    /**
      * Returns the insets of the screen closest to the display given by the context
      */
     private Rect getInsets(Context context) {
diff --git a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
index 83003ff..1a7d797 100644
--- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
@@ -20,6 +20,7 @@
 import android.view.View;
 import android.view.View.OnFocusChangeListener;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 
 /**
@@ -29,7 +30,8 @@
         implements OnFocusChangeListener {
 
     public FocusIndicatorHelper(View container) {
-        super(container, container.getResources().getColor(R.color.focused_background));
+        super(container, container.getResources().getColor(Flags.enableFocusOutline()
+                ? R.color.focus_outline_color : R.color.focused_background));
     }
 
     @Override
@@ -53,7 +55,18 @@
 
         @Override
         public void viewToRect(View v, Rect outRect) {
-            outRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
+            if (Flags.enableFocusOutline()) {
+                // Ensure the left and top would not be negative and drawn outside of canvas
+                outRect.set(Math.max(0, v.getLeft()), Math.max(0, v.getTop()), v.getRight(),
+                        v.getBottom());
+                // Stroke is drawn with half outside and half inside the view. Inset by half
+                // stroke width to move the whole stroke inside the view and avoid other views
+                // occluding it
+                int halfStrokeWidth = (int) mPaint.getStrokeWidth() / 2;
+                outRect.inset(halfStrokeWidth, halfStrokeWidth);
+            } else {
+                outRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
+            }
         }
     }
 }
diff --git a/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
index 2dc8d81..8eb5c7d 100644
--- a/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
@@ -29,6 +29,7 @@
 import android.util.FloatProperty;
 import android.view.View;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 
 /**
@@ -97,13 +98,22 @@
         mContainer = container;
 
         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mMaxAlpha = Color.alpha(color);
         mPaint.setColor(0xFF000000 | color);
+        if (Flags.enableFocusOutline()) {
+            mPaint.setStyle(Paint.Style.STROKE);
+            mPaint.setStrokeWidth(container.getResources().getDimensionPixelSize(
+                    R.dimen.focus_outline_stroke_width));
+            mRadius = container.getResources().getDimensionPixelSize(
+                    R.dimen.focus_outline_radius);
+        } else {
+            mPaint.setStyle(Paint.Style.FILL);
+            mRadius = container.getResources().getDimensionPixelSize(
+                    R.dimen.grid_visualization_rounding_radius);
+        }
+        mMaxAlpha = Color.alpha(color);
 
         setAlpha(0);
         mShift = 0;
-        mRadius = container.getResources().getDimensionPixelSize(
-                R.dimen.grid_visualization_rounding_radius);
     }
 
     protected void setAlpha(float alpha) {
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index 45174a7..cbc6f44 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -23,10 +23,10 @@
 import com.android.launcher3.BubbleTextView
 import com.android.launcher3.allapps.BaseAllAppsAdapter
 import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.util.ExecutorRunnable
 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
 import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
 import com.android.launcher3.views.ActivityContext
-import java.util.concurrent.Future
 
 const val PREINFLATE_ICONS_ROW_COUNT = 4
 const val EXTRA_ICONS_COUNT = 2
@@ -38,9 +38,8 @@
  */
 class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
 
-    private var future: Future<Void>? = null
-
     var hasWorkProfile = false
+    var executorRunnable: ExecutorRunnable<List<ViewHolder>>? = null
 
     /**
      * Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate.
@@ -63,21 +62,38 @@
                 override fun getLayoutManager(): RecyclerView.LayoutManager? = null
             }
 
-        // Inflate view holders on background thread, and added to view pool on main thread.
-        future?.cancel(true)
-        future =
-            VIEW_PREINFLATION_EXECUTOR.submit<Void> {
-                val viewHolders =
-                    Array(preInflateCount) {
-                        adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON)
+        executorRunnable?.cancel(/* interrupt= */ true)
+        executorRunnable =
+            ExecutorRunnable.createAndExecute(
+                VIEW_PREINFLATION_EXECUTOR,
+                {
+                    val list: ArrayList<ViewHolder> = ArrayList()
+                    for (i in 0 until preInflateCount) {
+                        if (Thread.interrupted()) {
+                            break
+                        }
+                        list.add(
+                            adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON)
+                        )
                     }
-                MAIN_EXECUTOR.execute {
+                    list
+                },
+                MAIN_EXECUTOR,
+                { viewHolders ->
                     for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) {
                         putRecycledView(viewHolders[i])
                     }
                 }
-                null
-            }
+            )
+    }
+
+    /**
+     * When clearing [RecycledViewPool], we should also abort pre-inflation tasks. This will make
+     * sure we don't inflate app icons after DeviceProfile has changed.
+     */
+    override fun clear() {
+        super.clear()
+        executorRunnable?.cancel(/* interrupt= */ true)
     }
 
     /**
diff --git a/src/com/android/launcher3/util/ExecutorRunnable.kt b/src/com/android/launcher3/util/ExecutorRunnable.kt
new file mode 100644
index 0000000..49cf592
--- /dev/null
+++ b/src/com/android/launcher3/util/ExecutorRunnable.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import java.util.concurrent.Executor
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Future
+import java.util.function.Consumer
+import java.util.function.Supplier
+
+/** A [Runnable] that can be posted to a [Executor] that can be cancelled. */
+class ExecutorRunnable<T>
+private constructor(
+    private val task: Supplier<T>,
+    // Executor where consumer needs to be executed on. Typically UI executor.
+    private val callbackExecutor: Executor,
+    // Consumer that needs to be accepted upon completion of the task. Typically work that needs to
+    // be done in UI thread after task completes.
+    private val callback: Consumer<T>
+) : Runnable {
+
+    // future of this runnable that will used for cancellation.
+    lateinit var future: Future<*>
+
+    // flag to cancel the callback
+    var canceled = false
+
+    override fun run() {
+        val value: T = task.get()
+        callbackExecutor.execute {
+            if (!canceled) {
+                callback.accept(value)
+            }
+        }
+    }
+
+    /**
+     * Cancel the [ExecutorRunnable] if not scheduled. If [ExecutorRunnable] has started execution
+     * at this time, we will try to cancel the callback if not executed yet.
+     */
+    fun cancel(interrupt: Boolean) {
+        future.cancel(interrupt)
+        canceled = true
+    }
+
+    companion object {
+        /**
+         * Create [ExecutorRunnable] and execute it on task [Executor]. It will also save the
+         * [Future] into this [ExecutorRunnable] to be used for cancellation.
+         */
+        fun <T> createAndExecute(
+            // Executor where task will be executed, typically an Executor running on background
+            // thread.
+            taskExecutor: ExecutorService,
+            task: Supplier<T>,
+            callbackExecutor: Executor,
+            callback: Consumer<T>
+        ): ExecutorRunnable<T> {
+            val executorRunnable = ExecutorRunnable(task, callbackExecutor, callback)
+            executorRunnable.future = taskExecutor.submit(executorRunnable)
+            return executorRunnable
+        }
+    }
+}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 1aa49c7..5d069ed 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -19,7 +19,6 @@
 import android.annotation.TargetApi;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Handler;
@@ -32,7 +31,6 @@
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.AdapterView;
@@ -79,9 +77,6 @@
     private final CheckLongPressHelper mLongPressHelper;
     protected final Launcher mLauncher;
 
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private boolean mReinflateOnConfigChange;
-
     // Maintain the color manager.
     private final LocalColorExtractor mColorExtractor;
 
@@ -176,17 +171,6 @@
 
         // The provider info or the views might have changed.
         checkIfAutoAdvance();
-
-        // It is possible that widgets can receive updates while launcher is not in the foreground.
-        // Consequently, the widgets will be inflated for the orientation of the foreground activity
-        // (framework issue). On resuming, we ensure that any widgets are inflated for the current
-        // orientation.
-        mReinflateOnConfigChange = !isSameOrientation();
-    }
-
-    private boolean isSameOrientation() {
-        return mLauncher.getResources().getConfiguration().orientation ==
-                mLauncher.getOrientation();
     }
 
     private boolean checkScrollableRecursively(ViewGroup viewGroup) {
@@ -450,17 +434,6 @@
         scheduleNextAdvance();
     }
 
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-
-        // Only reinflate when the final configuration is same as the required configuration
-        if (mReinflateOnConfigChange && isSameOrientation()) {
-            mReinflateOnConfigChange = false;
-            reInflate();
-        }
-    }
-
     public void reInflate() {
         if (!isAttachedToWindow()) {
             return;
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
index ed34307..568fc9f 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
@@ -166,9 +166,11 @@
             mLauncher.getWorkspace().verifyWorkspaceAppIconIsGone(
                     DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
 
-            Map<String, Point> finalPositions =
-                    mLauncher.getWorkspace().getWorkspaceIconsPositions();
-            assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
+            if (!TestStabilityRule.isPresubmit()) { // b/315847371
+                Map<String, Point> finalPositions =
+                        mLauncher.getWorkspace().getWorkspaceIconsPositions();
+                assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
+            }
         } finally {
             TestUtil.uninstallDummyApp();
         }
diff --git a/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt b/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
new file mode 100644
index 0000000..b4591ba
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import java.util.concurrent.ExecutorService
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Unit test for [ExecutorRunnable] */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ExecutorRunnableTest {
+
+    private lateinit var underTest: ExecutorRunnable<Int>
+
+    private var result: Int = -1
+    private var isTaskExecuted = false
+    private var isCallbackExecuted = false
+
+    @Before
+    fun setup() {
+        reset()
+        underTest =
+            ExecutorRunnable.createAndExecute(
+                Executors.UI_HELPER_EXECUTOR,
+                {
+                    isTaskExecuted = true
+                    1
+                },
+                Executors.VIEW_PREINFLATION_EXECUTOR,
+                {
+                    isCallbackExecuted = true
+                    result = it + 1
+                }
+            )
+    }
+
+    @Test
+    fun run_and_complete() {
+        awaitAllExecutorCompleted()
+
+        assertTrue(isTaskExecuted)
+        assertTrue(isCallbackExecuted)
+        assertEquals(2, result)
+    }
+
+    @Test
+    fun run_and_cancel_cancelCallback() {
+        underTest.cancel(true)
+        awaitAllExecutorCompleted()
+
+        assertFalse(isCallbackExecuted)
+        assertEquals(0, result)
+    }
+
+    @Test
+    fun run_and_cancelAfterCompletion_executeAll() {
+        awaitAllExecutorCompleted()
+
+        underTest.cancel(true)
+
+        assertTrue(isTaskExecuted)
+        assertTrue(isCallbackExecuted)
+        assertEquals(2, result)
+    }
+
+    private fun awaitExecutorCompleted(executor: ExecutorService) {
+        executor.submit<Any> { null }.get()
+    }
+
+    private fun awaitAllExecutorCompleted() {
+        awaitExecutorCompleted(Executors.UI_HELPER_EXECUTOR)
+        awaitExecutorCompleted(Executors.VIEW_PREINFLATION_EXECUTOR)
+    }
+
+    private fun reset() {
+        result = 0
+        isTaskExecuted = false
+        isCallbackExecuted = false
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
index a1d8059..5ef82ca 100644
--- a/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
+++ b/tests/tapl/com/android/launcher3/tapl/KeyboardQuickSwitch.java
@@ -58,15 +58,15 @@
 
     private final LauncherInstrumentation mLauncher;
     private final LauncherInstrumentation.ContainerType mStartingContainerType;
-    private final boolean mExpectHomeKeyEventsOnDismiss;
+    private final boolean mIsHomeState;
 
     KeyboardQuickSwitch(
             LauncherInstrumentation launcher,
             LauncherInstrumentation.ContainerType startingContainerType,
-            boolean expectHomeKeyEventsOnDismiss) {
+            boolean isHomeState) {
         mLauncher = launcher;
         mStartingContainerType = startingContainerType;
-        mExpectHomeKeyEventsOnDismiss = expectHomeKeyEventsOnDismiss;
+        mIsHomeState = isHomeState;
     }
 
     /**
@@ -164,7 +164,7 @@
                 mLauncher.verifyContainerType(mStartingContainerType);
 
                 // Wait until the device has fully settled before unpressing the key code
-                if (mExpectHomeKeyEventsOnDismiss) {
+                if (mIsHomeState) {
                     mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_HOME_ALT_LEFT_UP);
                 }
                 mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
@@ -204,7 +204,14 @@
                 "want to launch focused task: "
                         + (expectedPackageName == null ? "Overview" : expectedPackageName))) {
             mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_KQS_ALT_LEFT_UP);
-            mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
+
+            if (expectedPackageName == null || !mIsHomeState) {
+                mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0);
+            } else {
+                mLauncher.executeAndWaitForLauncherStop(
+                        () -> mLauncher.unpressKeyCode(KeyEvent.KEYCODE_ALT_LEFT, 0),
+                        "unpressing left alt");
+            }
 
             try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
                     "un-pressed left alt")) {