Merge "[dev_option] Use DesktopModeFlags from core instead of Shell" into main
diff --git a/Android.bp b/Android.bp
index bcbd362..4e1c9d3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -17,7 +17,7 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-min_launcher3_sdk_version = "30"
+min_launcher3_sdk_version = "31"
 
 // Targets that don't inherit framework aconfig libs (i.e., those that don't set
 // `platform_apis: true`) must manually link them.
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 cd2d37c..02201c0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -51,6 +51,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;
@@ -211,6 +212,9 @@
      */
     @Override
     public void onLauncherVisibilityChanged(boolean isVisible) {
+        if (DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(mLauncher)) {
+            DisplayController.handleInfoChangeForLauncherVisibilityChanged(mLauncher);
+        }
         onLauncherVisibilityChanged(isVisible, false /* fromInit */);
     }
 
@@ -482,4 +486,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/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index 93cbdc7..26a1322 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -29,7 +29,6 @@
 import android.widget.RemoteViews;
 import android.window.SplashScreen;
 
-import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.ActivityOptionsWrapper;
@@ -66,7 +65,7 @@
         Pair<Intent, ActivityOptions> options = remoteResponse.getLaunchOptions(view);
         ActivityOptionsWrapper activityOptions = mLauncher.getAppTransitionManager()
                 .getActivityLaunchOptions(hostView, (ItemInfo) hostView.getTag());
-        if (Utilities.ATLEAST_S && !pendingIntent.isActivity()) {
+        if (!pendingIntent.isActivity()) {
             // In the event this pending intent eventually launches an activity, i.e. a trampoline,
             // use the Quickstep transition animation.
             try {
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..f7f3157 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) {
@@ -202,7 +219,7 @@
     }
 
     private void startBackgroundAnimation(boolean forTablet) {
-        if (!Utilities.ATLEAST_S || mVibrator == null) {
+        if (mVibrator == null) {
             return;
         }
         boolean supportsThud = mVibrator.areAllPrimitivesSupported(
@@ -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..995635f 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -43,16 +43,21 @@
 
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 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,9 +65,12 @@
 import java.io.IOException;
 import java.util.Optional;
 
+import javax.inject.Inject;
+
 /**
  * Utility class to log launcher settings changes
  */
+@LauncherAppSingleton
 public class SettingsChangeLogger implements
         DisplayController.DisplayInfoChangeListener, OnSharedPreferenceChangeListener,
         SafeCloseable {
@@ -70,8 +78,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 +92,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 +223,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/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
index 4ea7753..f17be05 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
@@ -32,7 +32,6 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.launcher3.widget.RoundedCornerEnforcement;
 
 import java.util.stream.IntStream;
 
@@ -171,8 +170,7 @@
 
     /** Corner radius from source view's outline, or enforced view. */
     private static float getOutlineRadius(LauncherAppWidgetHostView hostView, View v) {
-        if (RoundedCornerEnforcement.isRoundedCornerEnabled()
-                && hostView.hasEnforcedCornerRadius()) {
+        if (hostView.hasEnforcedCornerRadius()) {
             return hostView.getEnforcedCornerRadius();
         } else if (v.getOutlineProvider() instanceof RemoteViewOutlineProvider
                 && v.getClipToOutline()) {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index bb46a2c..f502e31 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -343,7 +343,7 @@
             };
 
     public static final int SCROLL_VIBRATION_PRIMITIVE =
-            Utilities.ATLEAST_S ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK : -1;
+            VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
     public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f;
     public static final VibrationEffect SCROLL_VIBRATION_FALLBACK =
             VibrationConstants.EFFECT_TEXTURE_TICK;
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-hr/strings.xml b/res/values-hr/strings.xml
index d9f2072..f62384c 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -189,7 +189,7 @@
     <string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtrirajte"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Nije uspjelo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privatni prostor"</string>
-    <string name="private_space_secondary_label" msgid="9203933341714508907">"Dodirnite da biste postavili ili otvorili"</string>
+    <string name="private_space_secondary_label" msgid="9203933341714508907">"Dodirnite za postavljanje ili otvaranje"</string>
     <string name="ps_container_title" msgid="4391796149519594205">"Privatno"</string>
     <string name="ps_container_settings" msgid="6059734123353320479">"Postavke privatnog prostora"</string>
     <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privatno, otključano."</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 3c9135b..ebd68f7 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -117,7 +117,7 @@
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ថត៖ <xliff:g id="NAME">%1$s</xliff:g>, ធាតុ <xliff:g id="SIZE">%2$d</xliff:g>"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ថត៖ <xliff:g id="NAME">%1$s</xliff:g>, ធាតុ <xliff:g id="SIZE">%2$d</xliff:g> ឬច្រើនជាងនេះ"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"គូកម្មវិធី៖ <xliff:g id="APP1">%1$s</xliff:g> និង <xliff:g id="APP2">%2$s</xliff:g>"</string>
-    <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ផ្ទាំងរូបភាព និងរចនាប័ទ្ម"</string>
+    <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ផ្ទាំងរូបភាព និងរចនាបថ"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"កែអេក្រង់ដើម"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"ការកំណត់​ទំព័រដើម"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"បានបិទដំណើរការដោយអ្នកគ្រប់គ្រងរបស់អ្នក"</string>
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/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0bc192d..b0ec9b0 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -448,12 +448,10 @@
                 .logStart(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION)
                 .logStart(LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE);
         // Only use a hard-coded cookie since we only want to trace this once.
-        if (Utilities.ATLEAST_S) {
-            Trace.beginAsyncSection(
-                    DISPLAY_WORKSPACE_TRACE_METHOD_NAME, DISPLAY_WORKSPACE_TRACE_COOKIE);
-            Trace.beginAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
-                    DISPLAY_ALL_APPS_TRACE_COOKIE);
-        }
+        Trace.beginAsyncSection(
+                DISPLAY_WORKSPACE_TRACE_METHOD_NAME, DISPLAY_WORKSPACE_TRACE_COOKIE);
+        Trace.beginAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
+                DISPLAY_ALL_APPS_TRACE_COOKIE);
         TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT);
         if (DEBUG_STRICT_MODE) {
             StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
@@ -732,13 +730,6 @@
     public void onEnterAnimationComplete() {
         super.onEnterAnimationComplete();
         mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE);
-        // Starting with Android S, onEnterAnimationComplete is sent immediately
-        // causing the surface to get removed before the animation completed (b/175345344).
-        // Instead we rely on next user touch event to remove the view and optionally a callback
-        // from system from Android T onwards.
-        if (!Utilities.ATLEAST_S) {
-            AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE);
-        }
     }
 
     @Override
@@ -2579,10 +2570,8 @@
     public void bindAllApplications(AppInfo[] apps, int flags,
             Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
         mModelCallbacks.bindAllApplications(apps, flags, packageUserKeytoUidMap);
-        if (Utilities.ATLEAST_S) {
-            Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
-                    DISPLAY_ALL_APPS_TRACE_COOKIE);
-        }
+        Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
+                DISPLAY_ALL_APPS_TRACE_COOKIE);
     }
 
     /**
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/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index d57f8a0..496d517 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -61,7 +61,7 @@
         AbstractFloatingView.closeOpenViews(
             launcher,
             true,
-            AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv()
+            AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv(),
         )
         workspaceLoading = true
 
@@ -76,7 +76,7 @@
             TAG,
             "startBinding: " +
                 "hotseat layout was vertical: ${launcher.hotseat?.isHasVerticalHotseat}" +
-                " and is setting to ${launcher.deviceProfile.isVerticalBarLayout}"
+                " and is setting to ${launcher.deviceProfile.isVerticalBarLayout}",
         )
         launcher.hotseat?.resetLayout(launcher.deviceProfile.isVerticalBarLayout)
         TraceHelper.INSTANCE.endSection()
@@ -88,14 +88,12 @@
         pendingTasks: RunnableList,
         onCompleteSignal: RunnableList,
         workspaceItemCount: Int,
-        isBindSync: Boolean
+        isBindSync: Boolean,
     ) {
-        if (Utilities.ATLEAST_S) {
-            Trace.endAsyncSection(
-                TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
-                TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE
-            )
-        }
+        Trace.endAsyncSection(
+            TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
+            TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE,
+        )
         synchronouslyBoundPages = boundPages
         pagesToBindSynchronously = LIntSet()
         clearPendingBinds()
@@ -149,14 +147,14 @@
         // Cache one page worth of icons
         launcher.viewCache.setCacheSize(
             R.layout.folder_application,
-            deviceProfile.numFolderColumns * deviceProfile.numFolderRows
+            deviceProfile.numFolderColumns * deviceProfile.numFolderRows,
         )
         launcher.viewCache.setCacheSize(R.layout.folder_page, 2)
         TraceHelper.INSTANCE.endSection()
         launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true)
         launcher.workspace.pageIndicator.setPauseScroll(
             /*pause=*/ false,
-            deviceProfile.isTwoPanels
+            deviceProfile.isTwoPanels,
         )
         TestEventEmitter.INSTANCE.get(launcher).sendEvent(TestEvent.WORKSPACE_FINISH_LOADING)
     }
@@ -182,7 +180,7 @@
         val snackbar =
             AbstractFloatingView.getOpenView<AbstractFloatingView>(
                 launcher,
-                AbstractFloatingView.TYPE_SNACKBAR
+                AbstractFloatingView.TYPE_SNACKBAR,
             )
         snackbar?.post { snackbar.close(true) }
     }
@@ -191,7 +189,7 @@
     override fun bindAllApplications(
         apps: Array<AppInfo?>?,
         flags: Int,
-        packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?
+        packageUserKeytoUidMap: Map<PackageUserKey?, Int?>?,
     ) {
         Preconditions.assertUIThread()
         val hadWorkApps = launcher.appsView.shouldShowTabs()
@@ -312,7 +310,7 @@
         val info =
             PendingAddWidgetInfo(
                 widgetsListBaseEntry.mWidgets[0].widgetInfo,
-                LauncherSettings.Favorites.CONTAINER_DESKTOP
+                LauncherSettings.Favorites.CONTAINER_DESKTOP,
             )
         launcher.addPendingItem(
             info,
@@ -320,14 +318,14 @@
             WorkspaceLayoutManager.FIRST_SCREEN_ID,
             intArrayOf(0, 0),
             info.spanX,
-            info.spanY
+            info.spanY,
         )
     }
 
     override fun bindScreens(orderedScreenIds: LIntArray) {
         launcher.workspace.pageIndicator.setPauseScroll(
             /*pause=*/ true,
-            launcher.deviceProfile.isTwoPanels
+            launcher.deviceProfile.isTwoPanels,
         )
         val firstScreenPosition = 0
         if (
@@ -354,7 +352,7 @@
     override fun bindAppsAdded(
         newScreens: LIntArray?,
         addNotAnimated: java.util.ArrayList<ItemInfo?>?,
-        addAnimated: java.util.ArrayList<ItemInfo?>?
+        addAnimated: java.util.ArrayList<ItemInfo?>?,
     ) {
         // Add the new screens
         if (newScreens != null) {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index fde7014..f8ac48a 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -122,9 +122,6 @@
     public static final String[] EMPTY_STRING_ARRAY = new String[0];
     public static final Person[] EMPTY_PERSON_ARRAY = new Person[0];
 
-    @ChecksSdkIntAtLeast(api = VERSION_CODES.S)
-    public static final boolean ATLEAST_S = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
-
     @ChecksSdkIntAtLeast(api = VERSION_CODES.TIRAMISU, codename = "T")
     public static final boolean ATLEAST_T = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
 
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/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 4af9e2f..40c0cc6 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -69,7 +69,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceLayoutManager;
 import com.android.launcher3.apppairs.AppPairIcon;
@@ -207,15 +206,12 @@
             mWorkspaceScreens.put(Workspace.SECOND_SCREEN_ID, rightPanel);
         }
 
-        if (Utilities.ATLEAST_S) {
-            WallpaperColors wallpaperColors = wallpaperColorsOverride != null
-                    ? wallpaperColorsOverride
-                    : WallpaperManager.getInstance(context).getWallpaperColors(FLAG_SYSTEM);
-            mWallpaperColorResources = wallpaperColors != null ? LocalColorExtractor.newInstance(
-                    context).generateColorsOverride(wallpaperColors) : null;
-        } else {
-            mWallpaperColorResources = null;
-        }
+        WallpaperColors wallpaperColors = wallpaperColorsOverride != null
+                ? wallpaperColorsOverride
+                : WallpaperManager.getInstance(context).getWallpaperColors(FLAG_SYSTEM);
+        mWallpaperColorResources = wallpaperColors != null
+                ? LocalColorExtractor.newInstance(context).generateColorsOverride(wallpaperColors)
+                : null;
         mAppWidgetHost = new LauncherPreviewAppWidgetHost(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/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java
index 3f88717..ac9f2d6 100644
--- a/src/com/android/launcher3/model/WidgetItem.java
+++ b/src/com/android/launcher3/model/WidgetItem.java
@@ -4,8 +4,6 @@
 import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
 import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX;
 
-import static com.android.launcher3.Utilities.ATLEAST_S;
-
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -48,7 +46,7 @@
         super(info.provider, info.getProfile());
 
         label = iconCache.getTitleNoCache(info);
-        description = ATLEAST_S ? info.loadDescription(context) : null;
+        description = info.loadDescription(context);
         widgetInfo = info;
         activityInfo = null;
 
@@ -107,7 +105,7 @@
     /** Returns whether this {@link WidgetItem} has a preview layout that can be used. */
     @SuppressLint("NewApi") // Already added API check.
     public boolean hasPreviewLayout() {
-        return ATLEAST_S && widgetInfo != null && widgetInfo.previewLayout != Resources.ID_NULL;
+        return widgetInfo != null && widgetInfo.previewLayout != Resources.ID_NULL;
     }
 
     /** Returns whether this {@link WidgetItem} is for a shortcut rather than an app widget. */
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 90e47d6..1f1e514 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -30,7 +30,6 @@
 import com.android.launcher3.InvariantDeviceProfile
 import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherSettings.Favorites
-import com.android.launcher3.Utilities
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
 import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.logging.FileLog
@@ -76,7 +75,7 @@
     private val pmHelper: PackageManagerHelper,
     private val iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>>,
     private val unlockedUsers: LongSparseArray<Boolean>,
-    private val allDeepShortcuts: MutableList<ShortcutInfo>
+    private val allDeepShortcuts: MutableList<ShortcutInfo>,
 ) {
 
     private val isSafeMode = app.isSafeModeEnabled
@@ -97,7 +96,7 @@
                 // User has been deleted, remove the item.
                 c.markDeleted(
                     "User has been deleted for item id=${c.id}",
-                    RestoreError.PROFILE_DELETED
+                    RestoreError.PROFILE_DELETED,
                 )
                 return
             }
@@ -168,7 +167,7 @@
                 FileLog.d(
                     TAG,
                     "Activity not enabled for id=${c.id}, component=$cn, user=${c.user}." +
-                        " Will attempt to find fallback Activity for targetPkg=$targetPkg."
+                        " Will attempt to find fallback Activity for targetPkg=$targetPkg.",
                 )
                 intent = pmHelper.getAppLaunchIntent(targetPkg, c.user)
                 if (intent != null) {
@@ -178,7 +177,7 @@
                     c.markDeleted(
                         "No Activities found for id=${c.id}, targetPkg=$targetPkg, component=$cn." +
                             " Unable to create launch Intent.",
-                        RestoreError.MISSING_INFO
+                        RestoreError.MISSING_INFO,
                     )
                     return
                 }
@@ -213,7 +212,7 @@
                             else -> {
                                 c.markDeleted(
                                     "removing app that is not restored and not installing. package: $targetPkg",
-                                    RestoreError.APP_NOT_INSTALLED
+                                    RestoreError.APP_NOT_INSTALLED,
                                 )
                                 return
                             }
@@ -238,7 +237,7 @@
                         // Do not wait for external media load anymore.
                         c.markDeleted(
                             "Invalid package removed: $targetPkg",
-                            RestoreError.APP_NOT_INSTALLED
+                            RestoreError.APP_NOT_INSTALLED,
                         )
                         return
                     }
@@ -270,7 +269,7 @@
                         // The shortcut is no longer valid.
                         c.markDeleted(
                             "Pinned shortcut not found from request. package=${key.packageName}, user=${c.user}",
-                            RestoreError.SHORTCUT_NOT_FOUND
+                            RestoreError.SHORTCUT_NOT_FOUND,
                         )
                         return
                     }
@@ -337,7 +336,7 @@
                     activityInfo,
                     userCache.getUserInfo(c.user),
                     ApiWrapper.INSTANCE[app.context],
-                    pmHelper
+                    pmHelper,
                 )
             }
             if (
@@ -445,7 +444,7 @@
                     ", id=${c.id}," +
                     ", appWidgetId=${c.appWidgetId}," +
                     ", component=${component}",
-                RestoreError.INVALID_LOCATION
+                RestoreError.INVALID_LOCATION,
             )
             return
         }
@@ -456,7 +455,7 @@
                     ", appWidgetId=${c.appWidgetId}," +
                     ", component=${component}," +
                     ", container=${c.container}",
-                RestoreError.INVALID_LOCATION
+                RestoreError.INVALID_LOCATION,
             )
             return
         }
@@ -470,7 +469,7 @@
             TAG,
             "processWidget: id=${c.id}" +
                 ", appWidgetId=${c.appWidgetId}" +
-                ", inflationResult=$inflationResult"
+                ", inflationResult=$inflationResult",
         )
         when (inflationResult.type) {
             WidgetInflater.TYPE_DELETE -> {
@@ -496,7 +495,7 @@
                             ", appWidgetId=${c.appWidgetId}" +
                             ", component=${component}" +
                             ", restoreFlag:=${c.restoreFlag}",
-                        RestoreError.APP_NOT_INSTALLED
+                        RestoreError.APP_NOT_INSTALLED,
                     )
                     return
                 } else if (
@@ -512,7 +511,7 @@
                     WidgetsModel.newPendingItemInfo(
                         app.context,
                         appWidgetInfo.providerName,
-                        appWidgetInfo.user
+                        appWidgetInfo.user,
                     )
                 iconCache.getTitleAndIconForApp(appWidgetInfo.pendingItemInfo, false)
             }
@@ -522,7 +521,7 @@
                     lapi,
                     app.context,
                     appWidgetInfo.spanX,
-                    appWidgetInfo.spanY
+                    appWidgetInfo.spanY,
                 )
         }
 
@@ -541,7 +540,7 @@
                     " processWidget: Widget ${lapi.component} minSizes not met: span=${appWidgetInfo.spanX}x${appWidgetInfo.spanY} minSpan=${lapi.minSpanX}x${lapi.minSpanY}," +
                         " id: ${c.id}," +
                         " appWidgetId: ${c.appWidgetId}," +
-                        " component=${component}"
+                        " component=${component}",
                 )
                 logWidgetInfo(app.invariantDeviceProfile, lapi)
             }
@@ -554,7 +553,7 @@
 
         private fun logWidgetInfo(
             idp: InvariantDeviceProfile,
-            widgetProviderInfo: LauncherAppWidgetProviderInfo
+            widgetProviderInfo: LauncherAppWidgetProviderInfo,
         ) {
             val cellSize = Point()
             for (deviceProfile in idp.supportedProfiles) {
@@ -565,7 +564,7 @@
                         " available height: ${deviceProfile.availableHeightPx}," +
                         " cellLayoutBorderSpacePx Horizontal: ${deviceProfile.cellLayoutBorderSpacePx.x}," +
                         " cellLayoutBorderSpacePx Vertical: ${deviceProfile.cellLayoutBorderSpacePx.y}," +
-                        " cellSize: $cellSize"
+                        " cellSize: $cellSize",
                 )
             }
             val widgetDimension = StringBuilder()
@@ -583,21 +582,19 @@
                 .append("defaultHeight: ")
                 .append(widgetProviderInfo.minHeight)
                 .append("\n")
-            if (Utilities.ATLEAST_S) {
-                widgetDimension
-                    .append("targetCellWidth: ")
-                    .append(widgetProviderInfo.targetCellWidth)
-                    .append("\n")
-                    .append("targetCellHeight: ")
-                    .append(widgetProviderInfo.targetCellHeight)
-                    .append("\n")
-                    .append("maxResizeWidth: ")
-                    .append(widgetProviderInfo.maxResizeWidth)
-                    .append("\n")
-                    .append("maxResizeHeight: ")
-                    .append(widgetProviderInfo.maxResizeHeight)
-                    .append("\n")
-            }
+            widgetDimension
+                .append("targetCellWidth: ")
+                .append(widgetProviderInfo.targetCellWidth)
+                .append("\n")
+                .append("targetCellHeight: ")
+                .append(widgetProviderInfo.targetCellHeight)
+                .append("\n")
+                .append("maxResizeWidth: ")
+                .append(widgetProviderInfo.maxResizeWidth)
+                .append("\n")
+                .append("maxResizeHeight: ")
+                .append(widgetProviderInfo.maxResizeHeight)
+                .append("\n")
             FileLog.d(TAG, widgetDimension.toString())
         }
     }
diff --git a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
index f4dda55..361f09d 100644
--- a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PIN_WIDGETS;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
-import static com.android.launcher3.Utilities.ATLEAST_S;
 
 import android.appwidget.AppWidgetHostView;
 import android.content.ComponentName;
@@ -233,16 +232,16 @@
         if (providerInfo.isConfigurationOptional()) {
             widgetFeatures |= FEATURE_OPTIONAL_CONFIGURATION;
         }
-        if (ATLEAST_S && providerInfo.previewLayout != Resources.ID_NULL) {
+        if (providerInfo.previewLayout != Resources.ID_NULL) {
             widgetFeatures |= FEATURE_PREVIEW_LAYOUT;
         }
-        if (ATLEAST_S && providerInfo.targetCellWidth > 0 || providerInfo.targetCellHeight > 0) {
+        if (providerInfo.targetCellWidth > 0 || providerInfo.targetCellHeight > 0) {
             widgetFeatures |= FEATURE_TARGET_CELL_SIZE;
         }
         if (providerInfo.minResizeWidth > 0 || providerInfo.minResizeHeight > 0) {
             widgetFeatures |= FEATURE_MIN_SIZE;
         }
-        if (ATLEAST_S && providerInfo.maxResizeWidth > 0 || providerInfo.maxResizeHeight > 0) {
+        if (providerInfo.maxResizeWidth > 0 || providerInfo.maxResizeHeight > 0) {
             widgetFeatures |= FEATURE_MAX_SIZE;
         }
         if (hostView instanceof LauncherAppWidgetHostView &&
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..afa5075 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.util;
 
-import static android.content.Intent.ACTION_CONFIGURATION_CHANGED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
@@ -33,7 +32,6 @@
 import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
 
 import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
 import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.content.Intent;
@@ -42,7 +40,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
-import android.os.Build;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -132,21 +129,15 @@
         }
 
         Display display = mDM.getDisplay(DEFAULT_DISPLAY);
-        if (Utilities.ATLEAST_S) {
-            mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
-            mWindowContext.registerComponentCallbacks(this);
-        } else {
-            mWindowContext = null;
-            mReceiver.register(mContext, ACTION_CONFIGURATION_CHANGED);
-        }
+        mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
+        mWindowContext.registerComponentCallbacks(this);
 
         // Initialize navigation mode change listener
         mReceiver.registerPkgActions(mContext, TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED);
 
         WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(context);
-        Context displayInfoContext = getDisplayInfoContext(display);
-        mInfo = new Info(displayInfoContext, wmProxy,
-                wmProxy.estimateInternalDisplayBounds(displayInfoContext));
+        mInfo = new Info(mWindowContext, wmProxy,
+                wmProxy.estimateInternalDisplayBounds(mWindowContext));
         FileLog.i(TAG, "(CTOR) perDisplayBounds: " + mInfo.mPerDisplayBounds);
     }
 
@@ -161,7 +152,7 @@
                                     && mInfo.mIsTaskbarPinnedInDesktopMode != prefs.get(
                                     TASKBAR_PINNING_IN_DESKTOP_MODE);
                     if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) {
-                        handleInfoChange(mWindowContext.getDisplay());
+                        handleInfoChange();
                     }
                 };
 
@@ -191,7 +182,14 @@
      * Handles info change for desktop mode.
      */
     public static void handleInfoChangeForDesktopMode(Context context) {
-        INSTANCE.get(context).handleInfoChange(context.getDisplay());
+        INSTANCE.get(context).handleInfoChange();
+    }
+
+    /**
+     * Handles info change for launcher visibility.
+     */
+    public static void handleInfoChangeForLauncherVisibilityChanged(Context context) {
+        INSTANCE.get(context).handleInfoChange();
     }
 
     /**
@@ -217,6 +215,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;
@@ -252,36 +257,22 @@
         if (mDestroyed) {
             return;
         }
-        boolean reconfigure = false;
         if (ACTION_OVERLAY_CHANGED.equals(intent.getAction())) {
-            reconfigure = true;
-        } else if (ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) {
-            Configuration config = mContext.getResources().getConfiguration();
-            reconfigure = mInfo.fontScale != config.fontScale
-                    || mInfo.densityDpi != config.densityDpi;
-        }
-
-        if (reconfigure) {
-            Log.d(TAG, "Configuration changed, notifying listeners");
-            Display display = mDM.getDisplay(DEFAULT_DISPLAY);
-            if (display != null) {
-                handleInfoChange(display);
-            }
+            Log.d(TAG, "Overlay changed, notifying listeners");
+            handleInfoChange();
         }
     }
 
     @UiThread
     @Override
-    @TargetApi(Build.VERSION_CODES.S)
     public final void onConfigurationChanged(Configuration config) {
         Log.d(TASKBAR_NOT_DESTROYED_TAG, "DisplayController#onConfigurationChanged: " + config);
-        Display display = mWindowContext.getDisplay();
         if (config.densityDpi != mInfo.densityDpi
                 || config.fontScale != mInfo.fontScale
-                || display.getRotation() != mInfo.rotation
                 || !mInfo.mScreenSizeDp.equals(
-                        new PortraitSize(config.screenHeightDp, config.screenWidthDp))) {
-            handleInfoChange(display);
+                        new PortraitSize(config.screenHeightDp, config.screenWidthDp))
+                || mWindowContext.getDisplay().getRotation() != mInfo.rotation) {
+            handleInfoChange();
         }
     }
 
@@ -304,17 +295,13 @@
         return mInfo;
     }
 
-    private Context getDisplayInfoContext(Display display) {
-        return Utilities.ATLEAST_S ? mWindowContext : mContext.createDisplayContext(display);
-    }
-
     @AnyThread
     @VisibleForTesting
-    public void handleInfoChange(Display display) {
+    public void handleInfoChange() {
         WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(mContext);
         Info oldInfo = mInfo;
 
-        Context displayInfoContext = getDisplayInfoContext(display);
+        Context displayInfoContext = mWindowContext;
         Info newInfo = new Info(displayInfoContext, wmProxy, oldInfo.mPerDisplayBounds);
 
         if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale
@@ -345,7 +332,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 +387,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 +451,8 @@
             mIsTaskbarPinnedInDesktopMode = LauncherPrefs.get(displayInfoContext).get(
                     TASKBAR_PINNING_IN_DESKTOP_MODE);
             mIsInDesktopMode = wmProxy.isInDesktopMode();
+            mShowLockedTaskbarOnHome = wmProxy.showLockedTaskbarOnHome(displayInfoContext);
+            mIsHomeVisible = wmProxy.isHomeVisible(displayInfoContext);
         }
 
         /**
@@ -476,6 +469,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 +540,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/EdgeEffectCompat.java b/src/com/android/launcher3/util/EdgeEffectCompat.java
index ca37259..a949f50 100644
--- a/src/com/android/launcher3/util/EdgeEffectCompat.java
+++ b/src/com/android/launcher3/util/EdgeEffectCompat.java
@@ -19,8 +19,6 @@
 import android.view.MotionEvent;
 import android.widget.EdgeEffect;
 
-import com.android.launcher3.Utilities;
-
 /**
  * Extension of {@link EdgeEffect} to allow backwards compatibility
  */
@@ -30,21 +28,6 @@
         super(context);
     }
 
-    @Override
-    public float getDistance() {
-        return Utilities.ATLEAST_S ? super.getDistance() : 0;
-    }
-
-    @Override
-    public float onPullDistance(float deltaDistance, float displacement) {
-        if (Utilities.ATLEAST_S) {
-            return super.onPullDistance(deltaDistance, displacement);
-        } else {
-            onPull(deltaDistance, displacement);
-            return deltaDistance;
-        }
-    }
-
     public float onPullDistance(float deltaDistance, float displacement, MotionEvent ev) {
         return onPullDistance(deltaDistance, displacement);
     }
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/OverlayEdgeEffect.java b/src/com/android/launcher3/util/OverlayEdgeEffect.java
index d09d801..0623af7 100644
--- a/src/com/android/launcher3/util/OverlayEdgeEffect.java
+++ b/src/com/android/launcher3/util/OverlayEdgeEffect.java
@@ -46,6 +46,7 @@
         return mDistance;
     }
 
+    @Override
     public float onPullDistance(float deltaDistance, float displacement) {
         // Fallback implementation, will never actually get called
         if (BuildConfig.IS_DEBUG_DEVICE) {
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 469e363..b1913c0 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -303,10 +303,7 @@
 
     /** Returns the incremental download progress for the given shortcut's app. */
     public static int getLoadingProgress(LauncherActivityInfo info) {
-        if (Utilities.ATLEAST_S) {
-            return (int) (100 * info.getLoadingProgress());
-        }
-        return 100;
+        return (int) (100 * info.getLoadingProgress());
     }
 
     /** Returns true in case app is installed on the device or in archived state. */
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index 60951ba..104040a 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -52,10 +52,8 @@
     }
 
     public static int getActivityThemeRes(Context context, int wallpaperColorHints) {
-        boolean supportsDarkText = Utilities.ATLEAST_S
-                && (wallpaperColorHints & HINT_SUPPORTS_DARK_TEXT) != 0;
-        boolean isMainColorDark = Utilities.ATLEAST_S
-                && (wallpaperColorHints & HINT_SUPPORTS_DARK_THEME) != 0;
+        boolean supportsDarkText = (wallpaperColorHints & HINT_SUPPORTS_DARK_TEXT) != 0;
+        boolean isMainColorDark = (wallpaperColorHints & HINT_SUPPORTS_DARK_THEME) != 0;
 
         if (Utilities.isDarkTheme(context)) {
             return supportsDarkText ? R.style.AppTheme_Dark_DarkText
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index a4b8eb0..adb8f9d 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -31,8 +31,6 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.launcher3.Utilities;
-
 /**
  * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
  */
@@ -129,7 +127,7 @@
 
     /** Indicates that Taskbar has been invoked. */
     public void vibrateForTaskbarUnstash() {
-        if (Utilities.ATLEAST_S && mVibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)) {
+        if (mVibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)) {
             VibrationEffect primitiveLowTickEffect = VibrationEffect
                     .startComposition()
                     .addPrimitive(PRIMITIVE_LOW_TICK, LOW_TICK_SCALE)
diff --git a/src/com/android/launcher3/util/WallpaperColorHints.kt b/src/com/android/launcher3/util/WallpaperColorHints.kt
index 1361c1e..11d4c25 100644
--- a/src/com/android/launcher3/util/WallpaperColorHints.kt
+++ b/src/com/android/launcher3/util/WallpaperColorHints.kt
@@ -23,7 +23,6 @@
 import android.content.Context
 import androidx.annotation.MainThread
 import androidx.annotation.VisibleForTesting
-import com.android.launcher3.Utilities
 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
 import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
 
@@ -34,36 +33,34 @@
 class WallpaperColorHints(private val context: Context) : SafeCloseable {
     var hints: Int = 0
         private set
+
     private val wallpaperManager
         get() = context.getSystemService(WallpaperManager::class.java)!!
+
     private val onColorHintsChangedListeners = mutableListOf<OnColorHintListener>()
     private val onClose: SafeCloseable
 
     init {
-        if (Utilities.ATLEAST_S) {
-            hints = wallpaperManager.getWallpaperColors(FLAG_SYSTEM)?.colorHints ?: 0
-            val onColorsChangedListener = OnColorsChangedListener { colors, which ->
-                onColorsChanged(colors, which)
-            }
+        hints = wallpaperManager.getWallpaperColors(FLAG_SYSTEM)?.colorHints ?: 0
+        val onColorsChangedListener = OnColorsChangedListener { colors, which ->
+            onColorsChanged(colors, which)
+        }
+        UI_HELPER_EXECUTOR.execute {
+            wallpaperManager.addOnColorsChangedListener(
+                onColorsChangedListener,
+                MAIN_EXECUTOR.handler,
+            )
+        }
+        onClose = SafeCloseable {
             UI_HELPER_EXECUTOR.execute {
-                wallpaperManager.addOnColorsChangedListener(
-                    onColorsChangedListener,
-                    MAIN_EXECUTOR.handler
-                )
+                wallpaperManager.removeOnColorsChangedListener(onColorsChangedListener)
             }
-            onClose = SafeCloseable {
-                UI_HELPER_EXECUTOR.execute {
-                    wallpaperManager.removeOnColorsChangedListener(onColorsChangedListener)
-                }
-            }
-        } else {
-            onClose = SafeCloseable {}
         }
     }
 
     @MainThread
     private fun onColorsChanged(colors: WallpaperColors?, which: Int) {
-        if ((which and FLAG_SYSTEM) != 0 && Utilities.ATLEAST_S) {
+        if ((which and FLAG_SYSTEM) != 0) {
             val newHints = colors?.colorHints ?: 0
             if (newHints != hints) {
                 hints = newHints
@@ -86,6 +83,7 @@
         @VisibleForTesting
         @JvmField
         val INSTANCE = MainThreadInitializedObject { WallpaperColorHints(it) }
+
         @JvmStatic fun get(context: Context): WallpaperColorHints = INSTANCE.get(context)
     }
 }
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 0817c0a..84b4a36 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -32,7 +32,6 @@
 import static com.android.launcher3.util.RotationUtils.rotateRect;
 import static com.android.launcher3.util.RotationUtils.rotateSize;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -40,7 +39,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
-import android.os.Build;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.Display;
@@ -54,7 +52,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.NavigationMode;
@@ -122,6 +119,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) {
@@ -216,7 +227,7 @@
             int screenWidthPx,
             @NonNull WindowInsets windowInsets,
             @NonNull WindowInsets.Builder insetsBuilder) {
-        if (!isLargeScreen || !Utilities.ATLEAST_S) {
+        if (!isLargeScreen) {
             return;
         }
 
@@ -391,25 +402,16 @@
     /**
      * Returns a CachedDisplayInfo initialized for the current display
      */
-    @TargetApi(Build.VERSION_CODES.S)
     public CachedDisplayInfo getDisplayInfo(Context displayInfoContext) {
         int rotation = getRotation(displayInfoContext);
-        if (Utilities.ATLEAST_S) {
-            WindowMetrics windowMetrics = displayInfoContext.getSystemService(WindowManager.class)
-                    .getMaximumWindowMetrics();
-            return getDisplayInfo(windowMetrics, rotation);
-        } else {
-            Point size = new Point();
-            Display display = getDisplay(displayInfoContext);
-            display.getRealSize(size);
-            return new CachedDisplayInfo(size, rotation);
-        }
+        WindowMetrics windowMetrics = displayInfoContext.getSystemService(WindowManager.class)
+                .getMaximumWindowMetrics();
+        return getDisplayInfo(windowMetrics, rotation);
     }
 
     /**
      * Returns a CachedDisplayInfo initialized for the current display
      */
-    @TargetApi(Build.VERSION_CODES.S)
     protected CachedDisplayInfo getDisplayInfo(WindowMetrics windowMetrics, int rotation) {
         Point size = new Point(windowMetrics.getBounds().right, windowMetrics.getBounds().bottom);
         return new CachedDisplayInfo(size, rotation,
@@ -478,8 +480,7 @@
                 }
             }
         }
-        return Utilities.ATLEAST_S ? NavigationMode.NO_BUTTON :
-                NavigationMode.THREE_BUTTONS;
+        return NavigationMode.NO_BUTTON;
     }
 
     @Override
diff --git a/src/com/android/launcher3/views/SpringRelativeLayout.java b/src/com/android/launcher3/views/SpringRelativeLayout.java
index 923eb19..a13152e 100644
--- a/src/com/android/launcher3/views/SpringRelativeLayout.java
+++ b/src/com/android/launcher3/views/SpringRelativeLayout.java
@@ -25,8 +25,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory;
 
-import com.android.launcher3.Utilities;
-
 /**
  * View group to allow rendering overscroll effect in a child at the parent level
  */
@@ -46,10 +44,8 @@
 
     public SpringRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mEdgeGlowTop = Utilities.ATLEAST_S
-                ? new EdgeEffect(context, attrs) : new EdgeEffect(context);
-        mEdgeGlowBottom = Utilities.ATLEAST_S
-                ? new EdgeEffect(context, attrs) : new EdgeEffect(context);
+        mEdgeGlowTop = new EdgeEffect(context, attrs);
+        mEdgeGlowBottom = new EdgeEffect(context, attrs);
         setWillNotDraw(false);
     }
 
diff --git a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
index 856f4b3..12a14c2 100644
--- a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
@@ -104,7 +104,7 @@
 
     @UiThread
     private void enforceRoundedCorners() {
-        if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) {
+        if (mEnforcedCornerRadius <= 0) {
             resetRoundedCorners();
             return;
         }
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
index 2817299..ab42839 100644
--- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
+++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
@@ -183,19 +183,14 @@
 
                 // Draw horizontal and vertical lines to represent individual columns.
                 final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
+                boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */
+                        previewWidthF, /* bottom= */ previewHeightF);
 
-                if (Utilities.ATLEAST_S) {
-                    boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */
-                            previewWidthF, /* bottom= */ previewHeightF);
-
-                    p.setStyle(Paint.Style.FILL);
-                    p.setColor(Color.WHITE);
-                    float roundedCorner = mContext.getResources().getDimension(
-                            android.R.dimen.system_app_widget_background_radius);
-                    c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p);
-                } else {
-                    boxRect = drawBoxWithShadow(c, previewWidthF, previewHeightF);
-                }
+                p.setStyle(Paint.Style.FILL);
+                p.setColor(Color.WHITE);
+                float roundedCorner = mContext.getResources().getDimension(
+                        android.R.dimen.system_app_widget_background_radius);
+                c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p);
 
                 p.setStyle(Paint.Style.STROKE);
                 p.setStrokeWidth(mContext.getResources()
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
index 3e4fd8c..e77ba24 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -1,7 +1,5 @@
 package com.android.launcher3.widget;
 
-import static com.android.launcher3.Utilities.ATLEAST_S;
-
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
@@ -116,15 +114,13 @@
                     getSpanY(widgetPadding, minResizeHeight, dp.cellLayoutBorderSpacePx.y,
                             cellSize.y));
 
-            if (ATLEAST_S) {
-                if (maxResizeWidth > 0) {
-                    maxSpanX = Math.min(maxSpanX, getSpanX(widgetPadding, maxResizeWidth,
-                            dp.cellLayoutBorderSpacePx.x, cellSize.x));
-                }
-                if (maxResizeHeight > 0) {
-                    maxSpanY = Math.min(maxSpanY, getSpanY(widgetPadding, maxResizeHeight,
-                            dp.cellLayoutBorderSpacePx.y, cellSize.y));
-                }
+            if (maxResizeWidth > 0) {
+                maxSpanX = Math.min(maxSpanX, getSpanX(widgetPadding, maxResizeWidth,
+                        dp.cellLayoutBorderSpacePx.x, cellSize.x));
+            }
+            if (maxResizeHeight > 0) {
+                maxSpanY = Math.min(maxSpanY, getSpanY(widgetPadding, maxResizeHeight,
+                        dp.cellLayoutBorderSpacePx.y, cellSize.y));
             }
 
             spanX = Math.max(spanX,
@@ -135,18 +131,16 @@
                             cellSize.y));
         }
 
-        if (ATLEAST_S) {
-            // Ensures maxSpan >= minSpan
-            maxSpanX = Math.max(maxSpanX, minSpanX);
-            maxSpanY = Math.max(maxSpanY, minSpanY);
+        // Ensures maxSpan >= minSpan
+        maxSpanX = Math.max(maxSpanX, minSpanX);
+        maxSpanY = Math.max(maxSpanY, minSpanY);
 
-            // Use targetCellWidth/Height if it is within the min/max ranges.
-            // Otherwise, use the span of minWidth/Height.
-            if (targetCellWidth >= minSpanX && targetCellWidth <= maxSpanX
-                    && targetCellHeight >= minSpanY && targetCellHeight <= maxSpanY) {
-                spanX = targetCellWidth;
-                spanY = targetCellHeight;
-            }
+        // Use targetCellWidth/Height if it is within the min/max ranges.
+        // Otherwise, use the span of minWidth/Height.
+        if (targetCellWidth >= minSpanX && targetCellWidth <= maxSpanX
+                && targetCellHeight >= minSpanY && targetCellHeight <= maxSpanY) {
+            spanX = targetCellWidth;
+            spanY = targetCellHeight;
         }
 
         // If minSpanX/Y > spanX/Y, ignore the minSpanX/Y to match the behavior described in
@@ -213,8 +207,7 @@
     }
 
     public boolean isConfigurationOptional() {
-        return ATLEAST_S
-                && isReconfigurable()
+        return isReconfigurable()
                 && (getWidgetFeatures() & WIDGET_FEATURE_CONFIGURATION_OPTIONAL) != 0;
     }
 
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 8857774..130d533 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -136,9 +136,7 @@
                 Drawable p = new FastBitmapDrawable(new DatabaseWidgetPreviewLoader(launcher)
                         .generateWidgetPreview(
                                 createWidgetInfo.info, maxWidth, previewSizeBeforeScale));
-                if (RoundedCornerEnforcement.isRoundedCornerEnabled()) {
-                    p = new RoundDrawableWrapper(p, mEnforcedRoundedCornersForWidget);
-                }
+                p = new RoundDrawableWrapper(p, mEnforcedRoundedCornersForWidget);
                 preview = p;
             }
 
diff --git a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
index a2fac46..cadaf89 100644
--- a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
+++ b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
@@ -28,7 +28,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -70,11 +69,6 @@
         return background.getId() == android.R.id.background && background.getClipToOutline();
     }
 
-    /** Check if the app widget is in the deny list. */
-    public static boolean isRoundedCornerEnabled() {
-        return Utilities.ATLEAST_S;
-    }
-
     /**
      * Computes the rounded rectangle needed for this app widget.
      *
@@ -101,9 +95,6 @@
      * in the given context.
      */
     public static float computeEnforcedRadius(@NonNull Context context) {
-        if (!Utilities.ATLEAST_S) {
-            return 0;
-        }
         Resources res = context.getResources();
         float systemRadius = res.getDimension(android.R.dimen.system_app_widget_background_radius);
         float defaultRadius = res.getDimension(R.dimen.enforced_rounded_corner_max_radius);
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
index 41effa2..a749bb8 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
@@ -79,7 +81,7 @@
             WindowBounds(Rect(0, 0, width, height), Rect(0, inset, 0, 0), Surface.ROTATION_0),
             WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_90),
             WindowBounds(Rect(0, 0, width, height), Rect(0, inset, 0, 0), Surface.ROTATION_180),
-            WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_270)
+            WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_270),
         )
     private val configuration =
         Configuration(appContext.resources.configuration).apply {
@@ -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
@@ -167,7 +170,7 @@
     @UiThreadTest
     fun testTaskbarPinning() {
         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(true)
-        displayController.handleInfoChange(display)
+        displayController.handleInfoChange()
         verify(displayInfoChangeListener)
             .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
     }
@@ -176,8 +179,24 @@
     @UiThreadTest
     fun testTaskbarPinningChangeInDesktopMode() {
         whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false)
-        displayController.handleInfoChange(display)
+        displayController.handleInfoChange()
         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()
+        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);