Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 9c92655..4d6c7ab 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -418,4 +418,11 @@
     namespace: "launcher"
     description: "Support changing quiet mode for user profiles in taskbar."
     bug: "345760034"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "taskbar_overflow"
+    namespace: "launcher"
+    description: "Show recent apps in the taskbar overflow."
+    bug: "368119679"
+}
diff --git a/quickstep/dagger/LauncherAppComponent.java b/quickstep/dagger/LauncherAppComponent.java
index bd6008e..068f01c 100644
--- a/quickstep/dagger/LauncherAppComponent.java
+++ b/quickstep/dagger/LauncherAppComponent.java
@@ -18,6 +18,7 @@
 
 
 import com.android.quickstep.dagger.QuickStepModule;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
 
 import dagger.Component;
 
@@ -26,7 +27,7 @@
  */
 @LauncherAppSingleton
 @Component(modules = QuickStepModule.class)
-public interface LauncherAppComponent extends LauncherBaseAppComponent {
+public interface LauncherAppComponent extends QuickstepBaseAppComponent {
     /** Builder for quickstep LauncherAppComponent. */
     @Component.Builder
     interface Builder extends LauncherBaseAppComponent.Builder {
diff --git a/quickstep/res/layout/activity_allset.xml b/quickstep/res/layout/activity_allset.xml
index 685a151..625d9b3 100644
--- a/quickstep/res/layout/activity_allset.xml
+++ b/quickstep/res/layout/activity_allset.xml
@@ -96,7 +96,6 @@
                 style="@style/TextAppearance.GestureTutorial.Feedback.Subtitle"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:text="@string/allset_hint"
                 android:textSize="@dimen/allset_page_swipe_up_text_size"
                 android:gravity="center_horizontal"
 
diff --git a/quickstep/res/values-night/colors.xml b/quickstep/res/values-night/colors.xml
index 2052446..98e4871 100644
--- a/quickstep/res/values-night/colors.xml
+++ b/quickstep/res/values-night/colors.xml
@@ -25,7 +25,7 @@
     <color name="all_set_page_background">@android:color/system_neutral1_900</color>
 
     <!-- Turn on work apps button -->
-    <color name="work_turn_on_stroke">?androidprv:attr/colorAccentSecondaryVariant</color>
+    <color name="work_turn_on_stroke">?attr/materialColorPrimary</color>
     <color name="work_fab_bg_color">?attr/materialColorPrimaryFixedDim</color>
     <color name="work_fab_icon_color">?attr/materialColorOnPrimaryFixed</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 0bb971e..4c48bd3 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -94,7 +94,7 @@
     <color name="lottie_yellow600">#f9ab00</color>
 
     <!-- Turn on work apps button -->
-    <color name="work_turn_on_stroke">?androidprv:attr/colorAccentPrimaryVariant</color>
+    <color name="work_turn_on_stroke">?attr/materialColorPrimary</color>
     <color name="work_fab_bg_color">?attr/materialColorPrimaryFixedDim</color>
     <color name="work_fab_icon_color">?attr/materialColorOnPrimaryFixed</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 4d144ca..aa8cea5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -50,6 +50,7 @@
 import com.android.quickstep.util.TISBindHelper;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -210,6 +211,9 @@
      */
     @Override
     public void onLauncherVisibilityChanged(boolean isVisible) {
+        if (DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(mLauncher)) {
+            DisplayController.handleInfoChangeForLauncherVisibilityChanged(mLauncher);
+        }
         onLauncherVisibilityChanged(isVisible, false /* fromInit */);
     }
 
@@ -481,4 +485,13 @@
     protected String getTaskbarUIControllerName() {
         return "LauncherTaskbarUIController";
     }
+
+    @Override
+    public void onSwipeToUnstashTaskbar() {
+        // Once taskbar is unstashed, the user cannot return back to the overlay. We can
+        // clear it here to set the expected state once the user goes home.
+        if (mLauncher.getWorkspace().isOverlayShown()) {
+            mLauncher.getWorkspace().onOverlayScrollChanged(0);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 901b646..b95c406 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -1559,6 +1559,8 @@
      * @param delayTaskbarBackground whether we will delay the taskbar background animation
      */
     public void onSwipeToUnstashTaskbar(boolean delayTaskbarBackground) {
+        mControllers.uiController.onSwipeToUnstashTaskbar();
+
         boolean wasStashed = mControllers.taskbarStashController.isStashed();
         mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(/* stash= */ false,
                 SHOULD_BUBBLES_FOLLOW_DEFAULT_VALUE, delayTaskbarBackground);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 221504d..685c109 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -147,7 +147,11 @@
                 defaultTouchableRegion.addBoundsToRegion(bubbleBarViewController.bubbleBarBounds)
             }
         }
-        if (taskbarStashController.isInApp || taskbarStashController.isInOverview) {
+        if (
+            taskbarStashController.isInApp ||
+                taskbarStashController.isInOverview ||
+                DisplayController.showLockedTaskbarOnHome(context)
+        ) {
             // only add the taskbar touch region if not on home
             val bottom = windowLayoutParams.height
             val top = bottom - taskbarTouchableHeight
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 6bf1ee6..876221b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -53,6 +53,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.RecentsAnimationController;
@@ -299,6 +300,7 @@
         stashController.updateStateForFlag(FLAG_IN_APP, false);
 
         updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, true);
+        mLauncherState = toState;
         animatorSet.play(stashController.createApplyStateAnimator(duration));
         animatorSet.play(applyState(duration, false));
 
@@ -662,6 +664,9 @@
      * This refers to the intended state - a transition to this state might be in progress.
      */
     public boolean isTaskbarAlignedWithHotseat() {
+        if (DisplayController.showLockedTaskbarOnHome(mLauncher) && isInLauncher()) {
+            return false;
+        }
         return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
     }
 
@@ -673,8 +678,7 @@
             boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
             boolean willStashVisually = isInStashedState
                     && mControllers.taskbarStashController.supportsVisualStashing();
-            boolean isTaskbarAlignedWithHotseat =
-                    mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
+            boolean isTaskbarAlignedWithHotseat = isTaskbarAlignedWithHotseat();
             return isTaskbarAlignedWithHotseat && !willStashVisually;
         } else {
             return false;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 1b4db7a..78e7b47 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -60,6 +60,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
@@ -247,6 +248,7 @@
                 context,
                 navCallbacks,
                 SystemUiProxy.INSTANCE.get(mContext),
+                ContextualEduStatsManager.INSTANCE.get(mContext),
                 new Handler(),
                 AssistUtils.newInstance(mContext));
         mComponentCallbacks = new ComponentCallbacks() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 872a4d0..15c35b6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -45,12 +45,14 @@
 import androidx.annotation.StringRes;
 
 import com.android.launcher3.R;
+import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.AssistUtils;
+import com.android.systemui.contextualeducation.GestureType;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 
 import java.io.PrintWriter;
@@ -109,6 +111,7 @@
     private final Context mContext;
     private final TaskbarNavButtonCallbacks mCallbacks;
     private final SystemUiProxy mSystemUiProxy;
+    private final ContextualEduStatsManager mContextualEduStatsManager;
     private final Handler mHandler;
     private final AssistUtils mAssistUtils;
     @Nullable private StatsLogManager mStatsLogManager;
@@ -119,11 +122,13 @@
             Context context,
             TaskbarNavButtonCallbacks callbacks,
             SystemUiProxy systemUiProxy,
+            ContextualEduStatsManager contextualEduStatsManager,
             Handler handler,
             AssistUtils assistUtils) {
         mContext = context;
         mCallbacks = callbacks;
         mSystemUiProxy = systemUiProxy;
+        mContextualEduStatsManager = contextualEduStatsManager;
         mHandler = handler;
         mAssistUtils = assistUtils;
     }
@@ -137,14 +142,20 @@
         switch (buttonType) {
             case BUTTON_BACK:
                 logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
+                mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
+                        GestureType.BACK);
                 executeBack();
                 break;
             case BUTTON_HOME:
                 logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_TAP);
+                mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
+                        GestureType.HOME);
                 navigateHome();
                 break;
             case BUTTON_RECENTS:
                 logEvent(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP);
+                mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
+                        GestureType.OVERVIEW);
                 navigateToOverview();
                 break;
             case BUTTON_IME_SWITCH:
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 9b7d3bf..7624afb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -1052,6 +1052,9 @@
      */
     @Nullable
     public Animator createApplyStateAnimator(long duration) {
+        if (mActivity.isPhoneMode()) {
+            return null;
+        }
         return mStatePropertyHolder.createSetStateAnimator(mState, duration);
     }
 
@@ -1097,10 +1100,6 @@
 
     /** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */
     public void updateStateForSysuiFlags(long systemUiStateFlags, boolean skipAnim) {
-        if (mActivity.isPhoneMode()) {
-            return;
-        }
-
         long animDuration = TASKBAR_STASH_DURATION;
         long startDelay = 0;
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 9163246..9c8c2a9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -436,4 +436,10 @@
     /** Un-stash the hotseat instantly */
     public void unStashHotseatInstantly() {
     }
+
+    /**
+     * Called when we want to unstash taskbar when user performs swipes up gesture.
+     */
+    public void onSwipeToUnstashTaskbar() {
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 11e0ed5..1d9e492 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -45,6 +45,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.DisplayController;
@@ -53,6 +54,7 @@
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.OverviewToHomeAnim;
 import com.android.quickstep.views.RecentsView;
+import com.android.systemui.contextualeducation.GestureType;
 
 import java.util.function.BiConsumer;
 
@@ -219,6 +221,8 @@
             }
             if (mStartState != mEndState) {
                 logHomeGesture();
+                ContextualEduStatsManager.INSTANCE.get(mLauncher).updateEduStats(
+                        mSwipeDetector.isTrackpadGesture(), GestureType.HOME);
             }
             AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(mLauncher);
             if (topOpenView != null) {
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index f92c557..520bec3 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -80,14 +80,6 @@
      */
     private var keyboardTaskFocusIndex = -1
 
-    /**
-     * Whether we should incoming toggle commands while a previous toggle command is still ongoing.
-     * This serves as a rate-limiter to prevent overlapping animations that can clobber each other
-     * and prevent clean-up callbacks from running. This thus prevents a recurring set of bugs with
-     * janky recents animations and unresponsive home and overview buttons.
-     */
-    private var waitForToggleCommandComplete = false
-
     private val activityInterface: BaseActivityInterface<*, *>
         get() = overviewComponentObserver.activityInterface
 
@@ -174,12 +166,6 @@
      */
     @VisibleForTesting
     fun executeCommand(command: CommandInfo, onCallbackResult: () -> Unit): Boolean {
-        // This shouldn't happen if we execute 1 command per time.
-        if (waitForToggleCommandComplete && command.type == TOGGLE) {
-            Log.d(TAG, "executeCommand: $command - waiting for toggle command complete")
-            return true
-        }
-
         val recentsView = visibleRecentsView
         Log.d(TAG, "executeCommand: $command - visibleRecentsView: $recentsView")
         return if (recentsView != null) {
@@ -251,7 +237,6 @@
     ): Boolean {
         var callbackList: RunnableList? = null
         if (taskView != null) {
-            waitForToggleCommandComplete = true
             taskView.isEndQuickSwitchCuj = true
             callbackList = taskView.launchWithAnimation()
         }
@@ -260,13 +245,11 @@
             callbackList.add {
                 Log.d(TAG, "launching task callback: $command")
                 onCallbackResult()
-                waitForToggleCommandComplete = false
             }
             Log.d(TAG, "launching task - waiting for callback: $command")
             return false
         } else {
             recents.startHome()
-            waitForToggleCommandComplete = false
             return true
         }
     }
@@ -517,7 +500,6 @@
             pw.println("    pendingCommandType=${commandQueue.first().type}")
         }
         pw.println("  keyboardTaskFocusIndex=$keyboardTaskFocusIndex")
-        pw.println("  waitForToggleCommandComplete=$waitForToggleCommandComplete")
     }
 
     @VisibleForTesting
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java b/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java
index db29636..08345b8 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java
@@ -15,10 +15,8 @@
  */
 package com.android.quickstep.dagger;
 
-import com.android.quickstep.logging.LoggingModule;
-
 import dagger.Module;
 
-@Module(includes = {LoggingModule.class})
+@Module
 public class QuickStepModule {
 }
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
new file mode 100644
index 0000000..f2d5715
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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.quickstep.dagger;
+
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
+import com.android.quickstep.logging.SettingsChangeLogger;
+
+/**
+ * Launcher Quickstep base component for Dagger injection.
+ *
+ * This class is not actually annotated as a Dagger component, since it is not used directly as one.
+ * Doing so generates unnecessary code bloat.
+ *
+ * See {@link LauncherAppComponent} for the one actually used.
+ */
+public interface QuickstepBaseAppComponent extends LauncherBaseAppComponent {
+    SettingsChangeLogger getSettingsChangeLogger();
+}
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 1bec970..b56958a 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -62,6 +62,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
@@ -103,6 +104,9 @@
 
     private final AnimatedFloat mSwipeProgress = new AnimatedFloat(this::onSwipeProgressUpdate);
 
+    private final InvariantDeviceProfile.OnIDPChangeListener mOnIDPChangeListener =
+            modelPropertiesChanged -> updateHint();
+
     private TISBindHelper mTISBindHelper;
 
     private BgDrawable mBackground;
@@ -115,6 +119,8 @@
 
     private AnimatorPlaybackController mLauncherStartAnim = null;
 
+    private TextView mHintView;
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -167,12 +173,9 @@
             }
         });
 
-        TextView hint = findViewById(R.id.hint);
-        DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
-        if (!dp.isGestureMode) {
-            hint.setText(R.string.allset_button_hint);
-        }
-        hint.setAccessibilityDelegate(new SkipButtonAccessibilityDelegate());
+        mHintView = findViewById(R.id.hint);
+        mHintView.setAccessibilityDelegate(new SkipButtonAccessibilityDelegate());
+        updateHint();
 
         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
 
@@ -190,7 +193,21 @@
                         LOTTIE_TERTIARY_COLOR_TOKEN, R.color.all_set_bg_tertiary),
                 getTheme());
 
-        startBackgroundAnimation(dp.isTablet);
+        startBackgroundAnimation(getDP().isTablet);
+        getIDP().addOnChangeListener(mOnIDPChangeListener);
+    }
+
+    private InvariantDeviceProfile getIDP() {
+        return LauncherAppState.getInstance(this).getInvariantDeviceProfile();
+    }
+
+    private DeviceProfile getDP() {
+        return getIDP().getDeviceProfile(this);
+    }
+
+    private void updateHint() {
+        mHintView.setText(
+                getDP().isGestureMode ? R.string.allset_hint : R.string.allset_button_hint);
     }
 
     private void runOnUiHelperThread(Runnable runnable) {
@@ -311,6 +328,7 @@
     @Override
     protected void onDestroy() {
         super.onDestroy();
+        getIDP().removeOnChangeListener(mOnIDPChangeListener);
         mTISBindHelper.onDestroy();
         clearBinderOverride();
         if (mBackgroundAnimatorListener != null) {
diff --git a/quickstep/src/com/android/quickstep/logging/LoggingModule.java b/quickstep/src/com/android/quickstep/logging/LoggingModule.java
deleted file mode 100644
index 8fdf3c7..0000000
--- a/quickstep/src/com/android/quickstep/logging/LoggingModule.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2024 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.quickstep.logging;
-
-import android.content.Context;
-
-import com.android.launcher3.dagger.ApplicationContext;
-import com.android.launcher3.dagger.LauncherAppSingleton;
-
-import dagger.Module;
-import dagger.Provides;
-
-@Module
-public class LoggingModule {
-    @Provides
-    @LauncherAppSingleton
-    SettingsChangeLogger provideSettingsChangeLogger(@ApplicationContext Context context) {
-        return SettingsChangeLogger.INSTANCE.get(context);
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 717f6c8..c4ba5ed 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -43,16 +43,20 @@
 
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
+import com.android.launcher3.dagger.ApplicationContext;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.model.DeviceGridState;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.ExecutorUtil;
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SettingsCache;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -60,6 +64,8 @@
 import java.io.IOException;
 import java.util.Optional;
 
+import javax.inject.Inject;
+
 /**
  * Utility class to log launcher settings changes
  */
@@ -70,8 +76,8 @@
     /**
      * Singleton instance
      */
-    public static MainThreadInitializedObject<SettingsChangeLogger> INSTANCE =
-            new MainThreadInitializedObject<>(SettingsChangeLogger::new);
+    public static DaggerSingletonObject<SettingsChangeLogger> INSTANCE =
+            new DaggerSingletonObject<>(QuickstepBaseAppComponent::getSettingsChangeLogger);
 
     private static final String TAG = "SettingsChangeLogger";
     private static final String BOOLEAN_PREF = "SwitchPreference";
@@ -84,25 +90,31 @@
     private StatsLogManager.LauncherEvent mNotificationDotsEvent;
     private StatsLogManager.LauncherEvent mHomeScreenSuggestionEvent;
 
-    private SettingsChangeLogger(Context context) {
-        this(context, StatsLogManager.newInstance(context));
+    @Inject
+    SettingsChangeLogger(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
+        this(context, StatsLogManager.newInstance(context), tracker);
     }
 
     @VisibleForTesting
-    SettingsChangeLogger(Context context, StatsLogManager statsLogManager) {
+    SettingsChangeLogger(Context context, StatsLogManager statsLogManager,
+            DaggerSingletonTracker tracker) {
         mContext = context;
         mStatsLogManager = statsLogManager;
         mLoggablePrefs = loadPrefKeys(context);
-        DisplayController.INSTANCE.get(context).addChangeListener(this);
-        mNavMode = DisplayController.getNavigationMode(context);
 
-        getPrefs(context).registerOnSharedPreferenceChangeListener(this);
-        getDevicePrefs(context).registerOnSharedPreferenceChangeListener(this);
+        ExecutorUtil.executeSyncOnMainOrFail(() -> {
+            DisplayController.INSTANCE.get(context).addChangeListener(this);
+            mNavMode = DisplayController.getNavigationMode(context);
 
-        SettingsCache mSettingsCache = SettingsCache.INSTANCE.get(context);
-        mSettingsCache.register(NOTIFICATION_BADGING_URI,
-                this::onNotificationDotsChanged);
-        onNotificationDotsChanged(mSettingsCache.getValue(NOTIFICATION_BADGING_URI));
+            getPrefs(context).registerOnSharedPreferenceChangeListener(this);
+            getDevicePrefs(context).registerOnSharedPreferenceChangeListener(this);
+
+            SettingsCache settingsCache = SettingsCache.INSTANCE.get(context);
+            settingsCache.register(NOTIFICATION_BADGING_URI,
+                    this::onNotificationDotsChanged);
+            onNotificationDotsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI));
+            tracker.addCloseable(this);
+        });
     }
 
     private static ArrayMap<String, LoggablePref> loadPrefKeys(Context context) {
@@ -209,6 +221,8 @@
     public void close() {
         getPrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
         getDevicePrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
+        SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
+        settingsCache.unregister(NOTIFICATION_BADGING_URI, this::onNotificationDotsChanged);
     }
 
     @VisibleForTesting
diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
index c3270dc..f3b984b8 100644
--- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.util;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.content.Context;
@@ -30,6 +31,8 @@
 import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.util.window.CachedDisplayInfo;
 import com.android.launcher3.util.window.WindowManagerProxy;
+import com.android.quickstep.SystemUiProxy;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
 import java.util.List;
 import java.util.Set;
@@ -66,6 +69,24 @@
     }
 
     @Override
+    public boolean showLockedTaskbarOnHome(Context displayInfoContext) {
+        if (!DesktopModeStatus.canEnterDesktopMode(displayInfoContext)) {
+            return false;
+        }
+        if (!DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(displayInfoContext)) {
+            return false;
+        }
+        final boolean isFreeformDisplay = displayInfoContext.getResources().getConfiguration()
+                .windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+        return isFreeformDisplay;
+    }
+
+    @Override
+    public boolean isHomeVisible(Context context) {
+        return SystemUiProxy.INSTANCE.get(context).getHomeVisibilityState().isHomeVisible();
+    }
+
+    @Override
     public int getRotation(Context displayInfoContext) {
         return displayInfoContext.getResources().getConfiguration().windowConfiguration
                 .getRotation();
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
index 399aea6..02d6218 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -33,11 +34,13 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.AssistUtils;
+import com.android.systemui.contextualeducation.GestureType;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -52,6 +55,10 @@
 
     @Mock
     SystemUiProxy mockSystemUiProxy;
+
+    @Mock
+    ContextualEduStatsManager mockContextualEduStatsManager;
+
     @Mock
     TouchInteractionService mockService;
     @Mock
@@ -100,6 +107,7 @@
                 mockService,
                 mCallbacks,
                 mockSystemUiProxy,
+                mockContextualEduStatsManager,
                 mockHandler,
                 mockAssistUtils);
     }
@@ -111,6 +119,13 @@
     }
 
     @Test
+    public void testPressBack_updateContextualEduData() {
+        mNavButtonController.onButtonClick(BUTTON_BACK, mockView);
+        verify(mockContextualEduStatsManager, times(1))
+                .updateEduStats(/* isTrackpad= */ eq(false), eq(GestureType.BACK));
+    }
+
+    @Test
     public void testPressImeSwitcher() {
         mNavButtonController.init(mockTaskbarControllers);
         mNavButtonController.onButtonClick(BUTTON_IME_SWITCH, mockView);
@@ -195,12 +210,26 @@
     }
 
     @Test
+    public void testPressHome_updateContextualEduData() {
+        mNavButtonController.onButtonClick(BUTTON_HOME, mockView);
+        verify(mockContextualEduStatsManager, times(1))
+                .updateEduStats(/* isTrackpad= */ eq(false), eq(GestureType.HOME));
+    }
+
+    @Test
     public void testPressRecents() {
         mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
         assertThat(mOverviewToggleCount).isEqualTo(1);
     }
 
     @Test
+    public void testPressRecents_updateContextualEduData() {
+        mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
+        verify(mockContextualEduStatsManager, times(1))
+                .updateEduStats(/* isTrackpad= */ eq(false), eq(GestureType.OVERVIEW));
+    }
+
+    @Test
     public void testPressRecentsWithScreenPinned_noNavigationToOverview() {
         mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
         mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
index 7c48ea4..0a60774 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
@@ -34,6 +34,7 @@
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_ENABLED
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_THEMED_ICON_DISABLED
 import com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY
+import com.android.launcher3.util.DaggerSingletonTracker
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -62,6 +63,7 @@
     @Mock private lateinit var mMockLogger: StatsLogManager.StatsLogger
 
     @Captor private lateinit var mEventCaptor: ArgumentCaptor<StatsLogManager.EventEnum>
+    @Mock private lateinit var mTracker: DaggerSingletonTracker
 
     private var mDefaultThemedIcons = false
     private var mDefaultAllowRotation = false
@@ -79,7 +81,7 @@
         // To match the default value of ALLOW_ROTATION
         LauncherPrefs.get(mContext).put(item = ALLOW_ROTATION, value = false)
 
-        mSystemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager)
+        mSystemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager, mTracker)
     }
 
     @After
@@ -90,7 +92,7 @@
 
     @Test
     fun loggingPrefs_correctDefaultValue() {
-        val systemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager)
+        val systemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager, mTracker)
 
         assertThat(systemUnderTest.loggingPrefs[ALLOW_ROTATION_PREFERENCE_KEY]!!.defaultValue)
             .isFalse()
@@ -117,7 +119,7 @@
         LauncherPrefs.get(mContext).put(item = ALLOW_ROTATION, value = true)
 
         // This a new object so the values of mLoggablePrefs will be different
-        SettingsChangeLogger(mContext, mStatsLogManager).logSnapshot(mInstanceId)
+        SettingsChangeLogger(mContext, mStatsLogManager, mTracker).logSnapshot(mInstanceId)
 
         verify(mMockLogger, atLeastOnce()).log(mEventCaptor.capture())
         val capturedEvents = mEventCaptor.allValues
diff --git a/res/values/config.xml b/res/values/config.xml
index 507ce9a..701e64a 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -67,7 +67,6 @@
     <string name="main_process_initializer_class" translatable="false"></string>
     <string name="app_launch_tracker_class" translatable="false"></string>
     <string name="test_information_handler_class" translatable="false"></string>
-    <string name="launcher_activity_logic_class" translatable="false"></string>
     <string name="model_delegate_class" translatable="false"></string>
     <string name="window_manager_proxy_class" translatable="false"></string>
     <string name="secondary_display_predictions_class" translatable="false"></string>
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index 6622e11..17084bb 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -60,6 +60,7 @@
         mScrollbar = scrollbar;
         mScrollbar.setRecyclerView(this);
         mScrollbar.setFastScrollerLocation(location);
+        scrollToTop();
         onUpdateScrollbar(0);
     }
 
diff --git a/src/com/android/launcher3/LauncherApplication.java b/src/com/android/launcher3/LauncherApplication.java
index 490186a..4c82e56 100644
--- a/src/com/android/launcher3/LauncherApplication.java
+++ b/src/com/android/launcher3/LauncherApplication.java
@@ -18,6 +18,7 @@
 import android.app.Application;
 
 import com.android.launcher3.dagger.DaggerLauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppComponent;
 import com.android.launcher3.dagger.LauncherBaseAppComponent;
 
 /**
@@ -30,10 +31,18 @@
     public void onCreate() {
         super.onCreate();
         MainProcessInitializer.initialize(this);
-        mAppComponent = DaggerLauncherAppComponent.builder().appContext(this).build();
+        initDagger();
     }
 
-    public LauncherBaseAppComponent getAppComponent() {
-        return mAppComponent;
+    public LauncherAppComponent getAppComponent() {
+        // Since supertype setters will return a supertype.builder and @Component.Builder types
+        // must not have any generic types.
+        // We need to cast mAppComponent to {@link LauncherAppComponent} since appContext()
+        // method is defined in the super class LauncherBaseComponent#Builder.
+        return (LauncherAppComponent) mAppComponent;
+    }
+
+    protected void initDagger() {
+        mAppComponent = DaggerLauncherAppComponent.builder().appContext(this).build();
     }
 }
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 1a59d82..0a50e8b 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 
+import com.android.launcher3.util.DaggerSingletonTracker;
+
 import dagger.BindsInstance;
 
 /**
@@ -29,6 +31,7 @@
  * See {@link LauncherAppComponent} for the one actually used by AOSP.
  */
 public interface LauncherBaseAppComponent {
+    DaggerSingletonTracker getDaggerSingletonTracker();
     /** Builder for LauncherBaseAppComponent. */
     interface Builder {
         @BindsInstance Builder appContext(@ApplicationContext Context context);
diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java
deleted file mode 100644
index 30575fc..0000000
--- a/src/com/android/launcher3/icons/ComponentWithLabel.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2018 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.icons;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.icons.cache.CachingLogic;
-
-public interface ComponentWithLabel {
-
-    ComponentName getComponent();
-
-    UserHandle getUser();
-
-    CharSequence getLabel(PackageManager pm);
-
-
-    class ComponentCachingLogic<T extends ComponentWithLabel> implements CachingLogic<T> {
-
-        private final PackageManager mPackageManager;
-        private final boolean mAddToMemCache;
-
-        public ComponentCachingLogic(Context context, boolean addToMemCache) {
-            mPackageManager = context.getPackageManager();
-            mAddToMemCache = addToMemCache;
-        }
-
-        @Override
-        @NonNull
-        public ComponentName getComponent(@NonNull T object) {
-            return object.getComponent();
-        }
-
-        @NonNull
-        @Override
-        public UserHandle getUser(@NonNull T object) {
-            return object.getUser();
-        }
-
-        @NonNull
-        @Override
-        public CharSequence getLabel(@NonNull T object) {
-            return object.getLabel(mPackageManager);
-        }
-
-        @NonNull
-        @Override
-        public BitmapInfo loadIcon(@NonNull Context context, @NonNull T object) {
-            return BitmapInfo.LOW_RES_INFO;
-        }
-
-        @Override
-        public boolean addToMemCache() {
-            return mAddToMemCache;
-        }
-    }
-}
diff --git a/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java b/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java
deleted file mode 100644
index 0a52dd7..0000000
--- a/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2020 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.icons;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.icons.BaseIconFactory.IconOptions;
-
-/**
- * Extension of ComponentWithLabel to also support loading icons
- */
-public interface ComponentWithLabelAndIcon extends ComponentWithLabel {
-
-    /**
-     * Provide an icon for this object
-     */
-    Drawable getFullResIcon(IconCache cache);
-
-    class ComponentWithIconCachingLogic extends ComponentCachingLogic<ComponentWithLabelAndIcon> {
-
-        public ComponentWithIconCachingLogic(Context context, boolean addToMemCache) {
-            super(context, addToMemCache);
-        }
-
-        @NonNull
-        @Override
-        public BitmapInfo loadIcon(@NonNull Context context,
-                @NonNull ComponentWithLabelAndIcon object) {
-            Drawable d = object.getFullResIcon(LauncherAppState.getInstance(context)
-                    .getIconCache());
-            if (d == null) {
-                return super.loadIcon(context, object);
-            }
-            try (LauncherIcons li = LauncherIcons.obtain(context)) {
-                return li.createBadgedIconBitmap(d, new IconOptions().setUser(object.getUser()));
-            }
-        }
-    }
-}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 44e448e..587dc27 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -54,9 +54,10 @@
 import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
 import com.android.launcher3.icons.cache.BaseIconCache;
+import com.android.launcher3.icons.cache.CachedObjectCachingLogic;
 import com.android.launcher3.icons.cache.CachingLogic;
+import com.android.launcher3.icons.cache.LauncherActivityCachingLogic;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.IconRequestInfo;
@@ -102,7 +103,6 @@
     private final LauncherApps mLauncherApps;
     private final UserCache mUserManager;
     private final InstantAppResolver mInstantAppResolver;
-    private final IconProvider mIconProvider;
     private final CancellableTask mCancelledTask;
 
     private final SparseArray<BitmapInfo> mWidgetCategoryBitmapInfos;
@@ -112,14 +112,14 @@
     public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName,
             IconProvider iconProvider) {
         super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
-                idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */);
-        mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
-        mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
+                idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */, iconProvider);
+        mComponentWithLabelCachingLogic = new CachedObjectCachingLogic(
+                context, false /* loadIcons */, false /* addToMemCache */);
+        mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.INSTANCE;
         mShortcutCachingLogic = new ShortcutCachingLogic();
         mLauncherApps = mContext.getSystemService(LauncherApps.class);
         mUserManager = UserCache.INSTANCE.get(mContext);
         mInstantAppResolver = InstantAppResolver.newInstance(mContext);
-        mIconProvider = iconProvider;
         mWidgetCategoryBitmapInfos = new SparseArray<>();
 
         mCancelledTask = new CancellableTask(() -> null, MAIN_EXECUTOR, c -> { });
@@ -337,6 +337,9 @@
         }
     }
 
+    /**
+     * Loads and returns the icon for the provided object without adding it to memCache
+     */
     public synchronized String getTitleNoCache(ComponentWithLabel info) {
         CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info,
                 mComponentWithLabelCachingLogic, false /* usePackageIcon */,
@@ -629,12 +632,6 @@
                 info.getAppLabel());
     }
 
-    @Override
-    @NonNull
-    protected String getIconSystemState(String packageName) {
-        return mIconProvider.getSystemStateForPackage(mSystemState, packageName);
-    }
-
     /**
      * Interface for receiving itemInfo with high-res icon.
      */
diff --git a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
deleted file mode 100644
index de2269c..0000000
--- a/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2018 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.icons;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.LauncherActivityInfo;
-import android.os.Build;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.Flags;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
-import com.android.launcher3.icons.BaseIconFactory.IconOptions;
-import com.android.launcher3.icons.cache.CachingLogic;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/**
- * Caching logic for LauncherActivityInfo.
- */
-public class LauncherActivityCachingLogic
-        implements CachingLogic<LauncherActivityInfo>, ResourceBasedOverride {
-
-    /**
-     * Creates and returns a new instance
-     */
-    public static LauncherActivityCachingLogic newInstance(Context context) {
-        return Overrides.getObject(LauncherActivityCachingLogic.class, context,
-                R.string.launcher_activity_logic_class);
-    }
-
-    @NonNull
-    @Override
-    public ComponentName getComponent(@NonNull LauncherActivityInfo object) {
-        return object.getComponentName();
-    }
-
-    @NonNull
-    @Override
-    public UserHandle getUser(@NonNull LauncherActivityInfo object) {
-        return object.getUser();
-    }
-
-    @NonNull
-    @Override
-    public CharSequence getLabel(@NonNull LauncherActivityInfo object) {
-        return object.getLabel();
-    }
-
-    @NonNull
-    @Override
-    public BitmapInfo loadIcon(@NonNull Context context, @NonNull LauncherActivityInfo object) {
-        try (LauncherIcons li = LauncherIcons.obtain(context)) {
-            IconOptions iconOptions = new IconOptions().setUser(object.getUser());
-            iconOptions.mIsArchived = Flags.useNewIconForArchivedApps()
-                && Build.VERSION.SDK_INT >= 35
-                && object.getActivityInfo().isArchived;
-            return li.createBadgedIconBitmap(
-                    LauncherAppState.getInstance(context)
-                        .getIconProvider()
-                        .getIcon(object, li.mFillResIconDpi),
-                    iconOptions
-            );
-        }
-    }
-}
diff --git a/src/com/android/launcher3/icons/Legacy.kt b/src/com/android/launcher3/icons/Legacy.kt
new file mode 100644
index 0000000..3bf3bb2
--- /dev/null
+++ b/src/com/android/launcher3/icons/Legacy.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.icons
+
+import com.android.launcher3.icons.cache.CachedObject
+
+/**
+ * This files contains some definitions used during refactoring to avoid breaking changes.
+ *
+ * TODO(b/366237794) remove this file once refactoring is complete
+ */
+
+/** Temporary interface to allow easier refactoring */
+interface ComponentWithLabel : CachedObject<IconCache>
+
+/** Temporary interface to allow easier refactoring */
+interface ComponentWithLabelAndIcon : ComponentWithLabel
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
index f40eda6..7bb39e1 100644
--- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java
+++ b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
@@ -33,6 +33,7 @@
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.icons.BaseIconFactory.IconOptions;
+import com.android.launcher3.icons.cache.BaseIconCache;
 import com.android.launcher3.icons.cache.CachingLogic;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.Themes;
@@ -72,7 +73,8 @@
 
     @NonNull
     @Override
-    public BitmapInfo loadIcon(@NonNull Context context, @NonNull ShortcutInfo info) {
+    public BitmapInfo loadIcon(@NonNull Context context, @NonNull BaseIconCache cache,
+            @NonNull ShortcutInfo info) {
         try (LauncherIcons li = LauncherIcons.obtain(context)) {
             Drawable unbadgedDrawable = ShortcutCachingLogic.getIcon(
                     context, info, LauncherAppState.getIDP(context).fillResIconDpi);
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 605accf..609846f 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -70,11 +70,11 @@
 import com.android.launcher3.folder.FolderNameInfos;
 import com.android.launcher3.folder.FolderNameProvider;
 import com.android.launcher3.icons.ComponentWithLabelAndIcon;
-import com.android.launcher3.icons.ComponentWithLabelAndIcon.ComponentWithIconCachingLogic;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.LauncherActivityCachingLogic;
 import com.android.launcher3.icons.ShortcutCachingLogic;
+import com.android.launcher3.icons.cache.CachedObjectCachingLogic;
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
+import com.android.launcher3.icons.cache.LauncherActivityCachingLogic;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.AppPairInfo;
@@ -298,7 +298,7 @@
             IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
             setIgnorePackages(updateHandler);
             updateHandler.updateIcons(allActivityList,
-                    LauncherActivityCachingLogic.newInstance(mApp.getContext()),
+                    LauncherActivityCachingLogic.INSTANCE,
                     mApp.getModel()::onPackageIconsUpdated);
             logASplit("update icon cache");
 
@@ -360,7 +360,7 @@
             }
 
             updateHandler.updateIcons(allWidgetsList,
-                    new ComponentWithIconCachingLogic(mApp.getContext(), true),
+                    new CachedObjectCachingLogic(mApp.getContext()),
                     mApp.getModel()::onWidgetLabelsUpdated);
             logASplit("save widgets in icon cache");
 
diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java
index 667136a..47afeef 100644
--- a/src/com/android/launcher3/pm/PinRequestHelper.java
+++ b/src/com/android/launcher3/pm/PinRequestHelper.java
@@ -77,8 +77,9 @@
             WorkspaceItemInfo info = new WorkspaceItemInfo(si, context);
             // Apply the unbadged icon synchronously using the caching logic directly and
             // fetch the actual icon asynchronously.
-            info.bitmap = new ShortcutCachingLogic().loadIcon(context, si);
-            LauncherAppState.getInstance(context).getModel().updateAndBindWorkspaceItem(info, si);
+            LauncherAppState app = LauncherAppState.getInstance(context);
+            info.bitmap = new ShortcutCachingLogic().loadIcon(context, app.getIconCache(), si);
+            app.getModel().updateAndBindWorkspaceItem(info, si);
             return info;
         } else {
             return null;
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 3817563..efd1f0d 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -40,11 +40,13 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.FlingBlockCheck;
 import com.android.launcher3.util.TouchController;
+import com.android.systemui.contextualeducation.GestureType;
 
 /**
  * TouchController for handling state changes
@@ -388,6 +390,7 @@
         } else {
             logReachedState(mToState);
         }
+        updateContextualEduStats(targetState);
     }
 
     protected void goToTargetState(LauncherState targetState) {
@@ -403,6 +406,21 @@
                 .setDuration(0).start();
     }
 
+    private void updateContextualEduStats(LauncherState targetState) {
+        if (targetState == NORMAL) {
+            ContextualEduStatsManager.INSTANCE.get(
+                    mLauncher).updateEduStats(mDetector.isTrackpadGesture(), GestureType.HOME);
+        } else if (targetState == OVERVIEW) {
+            ContextualEduStatsManager.INSTANCE.get(
+                    mLauncher).updateEduStats(mDetector.isTrackpadGesture(), GestureType.OVERVIEW);
+        } else if (targetState == ALL_APPS && !mDetector.isTrackpadGesture()) {
+            // Only update if it is touch gesture as trackpad gesture is not relevant for all apps
+            // which only provides keyboard education.
+            ContextualEduStatsManager.INSTANCE.get(
+                    mLauncher).updateEduStats(/* isTrackpadGesture= */ false, GestureType.ALL_APPS);
+        }
+    }
+
     private void logReachedState(LauncherState targetState) {
         if (mStartState == targetState) {
             return;
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
index 52c3581..faac4a3 100644
--- a/src/com/android/launcher3/touch/BaseSwipeDetector.java
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -17,6 +17,8 @@
 
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 
+import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
+
 import android.content.Context;
 import android.graphics.PointF;
 import android.util.Log;
@@ -64,6 +66,7 @@
     protected PointF mSubtractDisplacement = new PointF();
     @VisibleForTesting ScrollState mState = ScrollState.IDLE;
     private boolean mIsSettingState;
+    protected boolean mIsTrackpadGesture;
 
     protected boolean mIgnoreSlopWhenSettling;
     protected Context mContext;
@@ -122,6 +125,10 @@
         return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING;
     }
 
+    public boolean isTrackpadGesture() {
+        return mIsTrackpadGesture;
+    }
+
     public void finishedScrolling() {
         setState(ScrollState.IDLE);
     }
@@ -147,7 +154,7 @@
                 mLastPos.set(mDownPos);
                 mLastDisplacement.set(0, 0);
                 mDisplacement.set(0, 0);
-
+                mIsTrackpadGesture = isTrackpadMotionEvent(ev);
                 if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) {
                     setState(ScrollState.DRAGGING);
                 }
diff --git a/src/com/android/launcher3/util/DaggerSingletonObject.java b/src/com/android/launcher3/util/DaggerSingletonObject.java
new file mode 100644
index 0000000..b8cf2ae
--- /dev/null
+++ b/src/com/android/launcher3/util/DaggerSingletonObject.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 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.content.Context;
+
+import com.android.launcher3.LauncherApplication;
+import com.android.launcher3.dagger.LauncherAppComponent;
+
+import java.util.function.Function;
+
+/**
+ * A class to provide DaggerSingleton objects in a traditional way for
+ * {@link MainThreadInitializedObject}.
+ * We should delete this class at the end and use @Inject to get dagger provided singletons.
+ */
+
+public class DaggerSingletonObject<T extends SafeCloseable> {
+    private final Function<LauncherAppComponent, T> mFunction;
+
+    public DaggerSingletonObject(Function<LauncherAppComponent, T> function) {
+        mFunction = function;
+    }
+
+    public T get(Context context) {
+        LauncherAppComponent component =
+                ((LauncherApplication) context.getApplicationContext()).getAppComponent();
+        return mFunction.apply(component);
+    }
+}
diff --git a/src/com/android/launcher3/util/DaggerSingletonTracker.java b/src/com/android/launcher3/util/DaggerSingletonTracker.java
new file mode 100644
index 0000000..2946da1
--- /dev/null
+++ b/src/com/android/launcher3/util/DaggerSingletonTracker.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 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 com.android.launcher3.dagger.LauncherAppSingleton;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+
+/**
+ * A tracker class for keeping track of Dagger created singletons.
+ * Dagger will take care of creating singletons. But we should take care of unregistering callbacks
+ * if at all registered during singleton construction.
+ * All singletons should be declared as SafeCloseable so that we can call close() method.
+ */
+@LauncherAppSingleton
+public class DaggerSingletonTracker implements SafeCloseable {
+
+    private final ArrayList<SafeCloseable> mLauncherAppSingletons = new ArrayList<>();
+
+    @Inject
+    DaggerSingletonTracker() {
+    }
+
+    /**
+     * Adds the SafeCloseable Singletons to the mLauncherAppSingletons list.
+     * This helps to track the singletons and close them appropriately.
+     * See {@link DaggerSingletonTracker#close()} and
+     * {@link MainThreadInitializedObject.SandboxContext#onDestroy()}
+     */
+    public void addCloseable(SafeCloseable closeable) {
+        mLauncherAppSingletons.add(closeable);
+    }
+
+    @Override
+    public void close() {
+        // Destroy in reverse order
+        for (int i = mLauncherAppSingletons.size() - 1; i >= 0; i--) {
+            mLauncherAppSingletons.get(i).close();
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 072bcdf..dff9a07 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -195,6 +195,13 @@
     }
 
     /**
+     * Handles info change for launcher visibility.
+     */
+    public static void handleInfoChangeForLauncherVisibilityChanged(Context context) {
+        INSTANCE.get(context).handleInfoChange(context.getDisplay());
+    }
+
+    /**
      * Enables transient taskbar status for tests.
      */
     @VisibleForTesting
@@ -217,6 +224,13 @@
         return INSTANCE.get(context).getInfo().isPinnedTaskbar();
     }
 
+    /**
+     * Returns whether the taskbar is forced to be pinned when home is visible.
+     */
+    public static boolean showLockedTaskbarOnHome(Context context) {
+        return INSTANCE.get(context).getInfo().showLockedTaskbarOnHome();
+    }
+
     @Override
     public void close() {
         mDestroyed = true;
@@ -345,7 +359,8 @@
         }
         if ((newInfo.mIsTaskbarPinned != oldInfo.mIsTaskbarPinned)
                 || (newInfo.mIsTaskbarPinnedInDesktopMode
-                    != oldInfo.mIsTaskbarPinnedInDesktopMode)) {
+                    != oldInfo.mIsTaskbarPinnedInDesktopMode)
+                || newInfo.isPinnedTaskbar() != oldInfo.isPinnedTaskbar()) {
             change |= CHANGE_TASKBAR_PINNING;
         }
         if (newInfo.mIsInDesktopMode != oldInfo.mIsInDesktopMode) {
@@ -399,6 +414,9 @@
 
         private final boolean mIsInDesktopMode;
 
+        private final boolean mShowLockedTaskbarOnHome;
+        private final boolean mIsHomeVisible;
+
         public Info(Context displayInfoContext) {
             /* don't need system overrides for external displays */
             this(displayInfoContext, new WindowManagerProxy(), new ArrayMap<>());
@@ -460,6 +478,8 @@
             mIsTaskbarPinnedInDesktopMode = LauncherPrefs.get(displayInfoContext).get(
                     TASKBAR_PINNING_IN_DESKTOP_MODE);
             mIsInDesktopMode = wmProxy.isInDesktopMode();
+            mShowLockedTaskbarOnHome = wmProxy.showLockedTaskbarOnHome(displayInfoContext);
+            mIsHomeVisible = wmProxy.isHomeVisible(displayInfoContext);
         }
 
         /**
@@ -476,6 +496,10 @@
                 return sTransientTaskbarStatusForTests;
             }
             if (enableTaskbarPinning()) {
+                // If Launcher is visible on the freeform display, ensure the taskbar is pinned.
+                if (mShowLockedTaskbarOnHome && mIsHomeVisible) {
+                    return false;
+                }
                 if (mIsInDesktopMode) {
                     return !mIsTaskbarPinnedInDesktopMode;
                 }
@@ -543,6 +567,13 @@
                 return TYPE_PHONE;
             }
         }
+
+        /**
+         * Returns whether the taskbar is forced to be pinned when home is visible.
+         */
+        public boolean showLockedTaskbarOnHome() {
+            return mShowLockedTaskbarOnHome;
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/util/ExecutorUtil.java b/src/com/android/launcher3/util/ExecutorUtil.java
new file mode 100644
index 0000000..efc0eec
--- /dev/null
+++ b/src/com/android/launcher3/util/ExecutorUtil.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 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 static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.os.Looper;
+
+import java.util.concurrent.ExecutionException;
+
+public final class ExecutorUtil {
+
+    /**
+     * Executes runnable on {@link Looper#getMainLooper()}, otherwise fails with an exception.
+     */
+    public static void executeSyncOnMainOrFail(Runnable runnable) {
+        try {
+            MAIN_EXECUTOR.submit(runnable).get();
+        } catch (InterruptedException | ExecutionException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
index 63f14bd..e12ccbc 100644
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -18,13 +18,13 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.os.Looper;
 import android.util.Log;
 
 import androidx.annotation.UiThread;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.LauncherApplication;
 import com.android.launcher3.util.ResourceBasedOverride.Overrides;
 
 import java.util.ArrayList;
@@ -118,7 +118,7 @@
      * Abstract Context which allows custom implementations for
      * {@link MainThreadInitializedObject} providers
      */
-    public static class SandboxContext extends ContextWrapper implements SandboxApplication {
+    public static class SandboxContext extends LauncherApplication implements SandboxApplication {
 
         private static final String TAG = "SandboxContext";
 
@@ -129,7 +129,8 @@
         private boolean mDestroyed = false;
 
         public SandboxContext(Context base) {
-            super(base);
+            attachBaseContext(base);
+            initDagger();
         }
 
         @Override
@@ -138,6 +139,7 @@
         }
 
         public void onDestroy() {
+            getAppComponent().getDaggerSingletonTracker().close();
             synchronized (mDestroyLock) {
                 // Destroy in reverse order
                 for (int i = mOrderedObjects.size() - 1; i >= 0; i--) {
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 0817c0a..22ecf68 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -122,6 +122,20 @@
     }
 
     /**
+     * Returns if the pinned taskbar should be shown when home is visible.
+     */
+    public boolean showLockedTaskbarOnHome(Context displayInfoContext) {
+        return false;
+    }
+
+    /**
+     * Returns if the home is visible.
+     */
+    public boolean isHomeVisible(Context context) {
+        return false;
+    }
+
+    /**
      * Returns the real bounds for the provided display after applying any insets normalization
      */
     public WindowBounds getRealBounds(Context displayInfoContext, CachedDisplayInfo info) {
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
index 41effa2..d4e4bd2 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -38,6 +38,8 @@
 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
 import com.android.launcher3.util.window.CachedDisplayInfo
 import com.android.launcher3.util.window.WindowManagerProxy
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
 import kotlin.math.min
 import org.junit.Before
 import org.junit.Test
@@ -111,6 +113,7 @@
         whenever(windowManagerProxy.getRealBounds(any(), any())).thenAnswer { i ->
             bounds[i.getArgument<CachedDisplayInfo>(1).rotation]
         }
+        whenever(windowManagerProxy.showLockedTaskbarOnHome(any())).thenReturn(false)
 
         whenever(windowManagerProxy.getNavigationMode(any())).thenReturn(NavigationMode.NO_BUTTON)
         // Mock context
@@ -180,4 +183,20 @@
         verify(displayInfoChangeListener)
             .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
     }
+
+    @Test
+    @UiThreadTest
+    fun testTaskbarPinningChangeInLockedTaskbarChange() {
+        whenever(windowManagerProxy.showLockedTaskbarOnHome(any())).thenReturn(true)
+        whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(true)
+        whenever(windowManagerProxy.isInDesktopMode()).thenReturn(false)
+        whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
+        DisplayController.enableTaskbarModePreferenceForTests(true)
+
+        assertTrue(displayController.getInfo().isTransientTaskbar())
+        displayController.handleInfoChange(display)
+        verify(displayInfoChangeListener)
+            .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
+        assertFalse(displayController.getInfo().isTransientTaskbar())
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
index b2e413d..b38dd4b 100644
--- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
@@ -45,7 +45,6 @@
 import com.android.launcher3.allapps.WorkProfileManager;
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.util.TestUtil;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 
 import org.junit.After;
@@ -147,7 +146,6 @@
 
     // Staging; will be promoted to presubmit if stable
     @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
-    @ScreenRecord
     @Test
     public void toggleWorks() {
         assumeTrue(mWorkProfileSetupSuccessful);
@@ -195,7 +193,6 @@
 
     }
 
-    @ScreenRecord // b/322823478
     @Test
     public void testEdu() {
         assumeTrue(mWorkProfileSetupSuccessful);