Merge "Moving DisplayController to dagger" into main
diff --git a/quickstep/src/com/android/launcher3/dagger/Modules.kt b/quickstep/src/com/android/launcher3/dagger/Modules.kt
new file mode 100644
index 0000000..04c1d5e
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/dagger/Modules.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dagger
+
+import com.android.launcher3.uioverrides.SystemApiWrapper
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl
+import com.android.launcher3.util.ApiWrapper
+import com.android.launcher3.util.PluginManagerWrapper
+import com.android.launcher3.util.window.WindowManagerProxy
+import com.android.quickstep.util.SystemWindowManagerProxy
+import dagger.Binds
+import dagger.Module
+
+private object Modules {}
+
+@Module
+abstract class WindowManagerProxyModule {
+    @Binds abstract fun bindWindowManagerProxy(proxy: SystemWindowManagerProxy): WindowManagerProxy
+}
+
+@Module
+abstract class ApiWrapperModule {
+    @Binds abstract fun bindApiWrapper(systemApiWrapper: SystemApiWrapper): ApiWrapper
+}
+
+@Module
+abstract class PluginManagerWrapperModule {
+    @Binds
+    abstract fun bindPluginManagerWrapper(impl: PluginManagerWrapperImpl): PluginManagerWrapper
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 3fa0e8e..7c6c7ac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -61,7 +61,6 @@
 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.statemanager.StatefulActivity;
 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
 import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
@@ -270,7 +269,6 @@
                 context,
                 navCallbacks,
                 SystemUiProxy.INSTANCE.get(mWindowContext),
-                ContextualEduStatsManager.INSTANCE.get(mWindowContext),
                 new Handler(),
                 new ContextualSearchInvoker(mWindowContext));
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index d1f9be0..1adb2e9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -51,7 +51,6 @@
 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;
@@ -120,7 +119,6 @@
     private final Context mContext;
     private final TaskbarNavButtonCallbacks mCallbacks;
     private final SystemUiProxy mSystemUiProxy;
-    private final ContextualEduStatsManager mContextualEduStatsManager;
     private final Handler mHandler;
     private final ContextualSearchInvoker mContextualSearchInvoker;
     @Nullable private StatsLogManager mStatsLogManager;
@@ -131,13 +129,11 @@
             Context context,
             TaskbarNavButtonCallbacks callbacks,
             SystemUiProxy systemUiProxy,
-            ContextualEduStatsManager contextualEduStatsManager,
             Handler handler,
             ContextualSearchInvoker contextualSearchInvoker) {
         mContext = context;
         mCallbacks = callbacks;
         mSystemUiProxy = systemUiProxy;
-        mContextualEduStatsManager = contextualEduStatsManager;
         mHandler = handler;
         mContextualSearchInvoker = contextualSearchInvoker;
     }
@@ -159,13 +155,13 @@
                 break;
             case BUTTON_HOME:
                 logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_TAP);
-                mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
+                mSystemUiProxy.updateContextualEduStats(/* isTrackpadGesture= */ false,
                         GestureType.HOME);
                 navigateHome();
                 break;
             case BUTTON_RECENTS:
                 logEvent(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP);
-                mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
+                mSystemUiProxy.updateContextualEduStats(/* isTrackpadGesture= */ false,
                         GestureType.OVERVIEW);
                 navigateToOverview();
                 break;
@@ -364,7 +360,7 @@
     private void executeBack(@Nullable KeyEvent keyEvent) {
         if (keyEvent == null || (keyEvent.getAction() == ACTION_UP && !keyEvent.isCanceled())) {
             logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
-            mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
+            mSystemUiProxy.updateContextualEduStats(/* isTrackpadGesture= */ false,
                     GestureType.BACK);
         }
         mSystemUiProxy.onBackEvent(keyEvent);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index d203f28..a4f8b81 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -45,11 +45,11 @@
 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;
 import com.android.launcher3.util.TouchController;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.OverviewToHomeAnim;
@@ -218,7 +218,7 @@
             }
             if (mStartState != mEndState) {
                 logHomeGesture();
-                ContextualEduStatsManager.INSTANCE.get(mLauncher).updateEduStats(
+                SystemUiProxy.INSTANCE.get(mLauncher).updateContextualEduStats(
                         mSwipeDetector.isTrackpadGesture(), GestureType.HOME);
             }
             AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(mLauncher);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index b562838..a74b350 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -37,6 +37,7 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
+import com.android.systemui.contextualeducation.GestureType;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 
 /**
@@ -163,8 +164,19 @@
     @Override
     protected void onSwipeInteractionCompleted(LauncherState targetState) {
         super.onSwipeInteractionCompleted(targetState);
+        SystemUiProxy sysUIProxy = SystemUiProxy.INSTANCE.get(mLauncher);
         if (mStartState == NORMAL && targetState == OVERVIEW) {
-            SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
+            sysUIProxy.onOverviewShown(true, TAG);
+        }
+
+        if (targetState == OVERVIEW) {
+            sysUIProxy.updateContextualEduStats(
+                    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.
+            sysUIProxy.updateContextualEduStats(
+                    /* isTrackpadGesture= */ false, GestureType.ALL_APPS);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 1029c67..98228ad 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -116,7 +116,6 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
@@ -1411,7 +1410,7 @@
             duration = mContainer != null && mContainer.getDeviceProfile().isTaskbarPresent
                     ? QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar)
                     : StaggeredWorkspaceAnim.DURATION_MS;
-            ContextualEduStatsManager.INSTANCE.get(mContext).updateEduStats(
+            SystemUiProxy.INSTANCE.get(mContext).updateContextualEduStats(
                     mGestureState.isTrackpadGesture(), GestureType.HOME);
         } else if (endTarget == RECENTS) {
             if (mRecentsView != null) {
@@ -1437,7 +1436,7 @@
                 if (!mGestureState.isHandlingAtomicEvent() || isScrolling) {
                     duration = Math.max(duration, mRecentsView.getScroller().getDuration());
                 }
-                ContextualEduStatsManager.INSTANCE.get(mContext).updateEduStats(
+                SystemUiProxy.INSTANCE.get(mContext).updateContextualEduStats(
                         mGestureState.isTrackpadGesture(), GestureType.OVERVIEW);
             }
         } else if (endTarget == LAST_TASK && mRecentsView != null
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
index 75694af..feb9107 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -62,6 +62,7 @@
 import com.android.quickstep.util.ActiveGestureProtoLogProxy
 import com.android.quickstep.util.ContextualSearchInvoker
 import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider
+import com.android.systemui.contextualeducation.GestureType
 import com.android.systemui.shared.recents.ISystemUiProxy
 import com.android.systemui.shared.recents.model.ThumbnailData.Companion.wrap
 import com.android.systemui.shared.system.QuickStepContract
@@ -215,9 +216,9 @@
             systemUiProxy?.onImeSwitcherLongPress()
         }
 
-    fun updateContextualEduStats(isTrackpadGesture: Boolean, gestureType: String) =
+    fun updateContextualEduStats(isTrackpadGesture: Boolean, gestureType: GestureType) =
         executeWithErrorLog({ "Failed call updateContextualEduStats" }) {
-            systemUiProxy?.updateContextualEduStats(isTrackpadGesture, gestureType)
+            systemUiProxy?.updateContextualEduStats(isTrackpadGesture, gestureType.name)
         }
 
     fun setHomeRotationEnabled(enabled: Boolean) =
diff --git a/quickstep/src/com/android/quickstep/contextualeducation/SystemContextualEduStatsManager.java b/quickstep/src/com/android/quickstep/contextualeducation/SystemContextualEduStatsManager.java
deleted file mode 100644
index 6a72537..0000000
--- a/quickstep/src/com/android/quickstep/contextualeducation/SystemContextualEduStatsManager.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 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.contextualeducation;
-
-import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
-import com.android.launcher3.dagger.LauncherAppSingleton;
-import com.android.quickstep.SystemUiProxy;
-import com.android.systemui.contextualeducation.GestureType;
-
-import javax.inject.Inject;
-
-/**
- * A class to update contextual education data via {@link SystemUiProxy}
- */
-@LauncherAppSingleton
-public class SystemContextualEduStatsManager extends ContextualEduStatsManager {
-    private final SystemUiProxy mSystemUiProxy;
-
-    @Inject
-    public SystemContextualEduStatsManager(SystemUiProxy systemUiProxy) {
-        mSystemUiProxy = systemUiProxy;
-    }
-
-    @Override
-    public void updateEduStats(boolean isTrackpadGesture, GestureType gestureType) {
-        mSystemUiProxy.updateContextualEduStats(isTrackpadGesture,
-                gestureType.name());
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java b/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java
deleted file mode 100644
index a6feff0..0000000
--- a/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java
+++ /dev/null
@@ -1,38 +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.dagger;
-
-import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
-import com.android.launcher3.uioverrides.SystemApiWrapper;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl;
-import com.android.launcher3.util.ApiWrapper;
-import com.android.launcher3.util.PluginManagerWrapper;
-import com.android.launcher3.util.window.WindowManagerProxy;
-import com.android.quickstep.contextualeducation.SystemContextualEduStatsManager;
-import com.android.quickstep.util.SystemWindowManagerProxy;
-
-import dagger.Binds;
-import dagger.Module;
-
-@Module
-public abstract class QuickStepModule {
-
-    @Binds abstract PluginManagerWrapper bindPluginManagerWrapper(PluginManagerWrapperImpl impl);
-    @Binds abstract ApiWrapper bindApiWrapper(SystemApiWrapper systemApiWrapper);
-    @Binds abstract ContextualEduStatsManager bindContextualEduStatsManager(
-            SystemContextualEduStatsManager manager);
-    @Binds abstract WindowManagerProxy bindWindowManagerProxy(SystemWindowManagerProxy proxy);
-}
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 c682990..a8f3500 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -40,7 +40,6 @@
 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;
@@ -65,9 +64,6 @@
     SystemUiProxy mockSystemUiProxy;
 
     @Mock
-    ContextualEduStatsManager mockContextualEduStatsManager;
-
-    @Mock
     TouchInteractionService mockService;
     @Mock
     Handler mockHandler;
@@ -118,7 +114,6 @@
                 mockService,
                 mCallbacks,
                 mockSystemUiProxy,
-                mockContextualEduStatsManager,
                 mockHandler,
                 mockContextualSearchInvoker);
     }
@@ -132,8 +127,8 @@
     @Test
     public void testPressBack_updateContextualEduData() {
         mNavButtonController.onButtonClick(BUTTON_BACK, mockView);
-        verify(mockContextualEduStatsManager, times(1))
-                .updateEduStats(/* isTrackpad= */ eq(false), eq(GestureType.BACK));
+        verify(mockSystemUiProxy, times(1))
+                .updateContextualEduStats(/* isTrackpad= */ eq(false), eq(GestureType.BACK));
     }
 
     @Test
@@ -223,8 +218,8 @@
     @Test
     public void testPressHome_updateContextualEduData() {
         mNavButtonController.onButtonClick(BUTTON_HOME, mockView);
-        verify(mockContextualEduStatsManager, times(1))
-                .updateEduStats(/* isTrackpad= */ eq(false), eq(GestureType.HOME));
+        verify(mockSystemUiProxy, times(1))
+                .updateContextualEduStats(/* isTrackpad= */ eq(false), eq(GestureType.HOME));
     }
 
     @Test
@@ -236,8 +231,8 @@
     @Test
     public void testPressRecents_updateContextualEduData() {
         mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
-        verify(mockContextualEduStatsManager, times(1))
-                .updateEduStats(/* isTrackpad= */ eq(false), eq(GestureType.OVERVIEW));
+        verify(mockSystemUiProxy, times(1))
+                .updateContextualEduStats(/* isTrackpad= */ eq(false), eq(GestureType.OVERVIEW));
     }
 
     @Test
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
index 74b154a..3cf912c 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
@@ -61,11 +61,10 @@
                 val mode = taskbarMode.mode
 
                 getInstrumentation().runOnMainSync {
-                    context.putObject(
-                        DisplayController.INSTANCE,
-                        object : DisplayController(context) {
-                            override fun getInfo(): Info {
-                                return spy(super.getInfo()) {
+                    DisplayController.INSTANCE[context].let {
+                        if (it is DisplayControllerSpy) {
+                            it.infoModifier = { info ->
+                                spy(info) {
                                     on { isTransientTaskbar } doReturn (mode == Mode.TRANSIENT)
                                     on { isPinnedTaskbar } doReturn (mode == Mode.PINNED)
                                     on { navigationMode } doReturn
@@ -76,8 +75,8 @@
                                         }
                                 }
                             }
-                        },
-                    )
+                        }
+                    }
                 }
 
                 base.evaluate()
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
index 31c5a4c..e6dc2a2 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
@@ -22,17 +22,25 @@
 import android.hardware.display.VirtualDisplay
 import android.view.Display.DEFAULT_DISPLAY
 import androidx.test.core.app.ApplicationProvider
-import com.android.launcher3.FakeLauncherPrefs
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.dagger.ApplicationContext
 import com.android.launcher3.dagger.LauncherAppComponent
-import com.android.launcher3.dagger.LauncherAppModule
 import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.AllModulesForTest
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.FakePrefsModule
 import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
 import com.android.launcher3.util.SandboxApplication
 import com.android.launcher3.util.SettingsCache
 import com.android.launcher3.util.SettingsCacheSandbox
+import com.android.launcher3.util.window.WindowManagerProxy
 import com.android.quickstep.SystemUiProxy
+import dagger.Binds
 import dagger.BindsInstance
 import dagger.Component
+import dagger.Module
+import javax.inject.Inject
 import org.junit.rules.ExternalResource
 import org.junit.rules.RuleChain
 import org.junit.rules.TestRule
@@ -113,11 +121,32 @@
     }
 }
 
+/** A wrapper over display controller which allows modifying the underlying info */
 @LauncherAppSingleton
-@Component(modules = [LauncherAppModule::class])
-interface TaskbarSandboxComponent : LauncherAppComponent {
+class DisplayControllerSpy
+@Inject
+constructor(
+    @ApplicationContext context: Context,
+    wmProxy: WindowManagerProxy,
+    prefs: LauncherPrefs,
+    lifecycle: DaggerSingletonTracker,
+) : DisplayController(context, wmProxy, prefs, lifecycle) {
 
-    override fun getLauncherPrefs(): FakeLauncherPrefs
+    var infoModifier: ((Info) -> Info)? = null
+
+    override fun getInfo(): Info = infoModifier?.invoke(super.getInfo()) ?: super.getInfo()
+}
+
+@Module
+abstract class DisplayControllerModule {
+    @Binds abstract fun bindDisplayController(controller: DisplayControllerSpy): DisplayController
+}
+
+@LauncherAppSingleton
+@Component(
+    modules = [AllModulesForTest::class, FakePrefsModule::class, DisplayControllerModule::class]
+)
+interface TaskbarSandboxComponent : LauncherAppComponent {
 
     @Component.Builder
     interface Builder : LauncherAppComponent.Builder {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
index 2b337c7..f05b422 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
@@ -119,7 +119,7 @@
         verify(systemUiProxy)
             .updateContextualEduStats(
                 /* isTrackpadGesture= */ eq(true),
-                eq(GestureType.HOME.toString()),
+                eq(GestureType.HOME),
             )
     }
 
@@ -129,7 +129,7 @@
         verify(systemUiProxy)
             .updateContextualEduStats(
                 /* isTrackpadGesture= */ eq(false),
-                eq(GestureType.HOME.toString()),
+                eq(GestureType.HOME),
             )
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
index dcb45e5..c1be1ce 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
@@ -21,8 +21,8 @@
 
 import com.android.launcher3.dagger.LauncherAppComponent;
 import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.util.AllModulesForTest;
 import com.android.launcher3.util.LauncherMultivalentJUnit;
-import com.android.quickstep.dagger.QuickStepModule;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.fallback.window.RecentsDisplayModel;
@@ -30,13 +30,13 @@
 import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
 import com.android.quickstep.views.RecentsViewContainer;
 
+import dagger.BindsInstance;
+import dagger.Component;
+
 import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
-import dagger.BindsInstance;
-import dagger.Component;
-
 @SmallTest
 @RunWith(LauncherMultivalentJUnit.class)
 public class RecentsWindowSwipeHandlerTestCase extends AbsSwipeUpHandlerTestCase<
@@ -83,7 +83,7 @@
     }
 
     @LauncherAppSingleton
-    @Component(modules = { QuickStepModule.class })
+    @Component(modules = {AllModulesForTest.class})
     interface TestComponent extends LauncherAppComponent {
         @Component.Builder
         interface Builder extends LauncherAppComponent.Builder {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
index 8879a01..7776351 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
@@ -46,8 +46,8 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.launcher3.dagger.LauncherAppComponent;
-import com.android.launcher3.dagger.LauncherAppModule;
 import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.util.AllModulesForTest;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 import com.android.quickstep.DeviceConfigWrapper;
@@ -461,12 +461,11 @@
     }
 
     @LauncherAppSingleton
-    @Component(modules = LauncherAppModule.class)
+    @Component(modules = AllModulesForTest.class)
     public interface TopTaskTrackerComponent extends LauncherAppComponent {
         @Component.Builder
         interface Builder extends LauncherAppComponent.Builder {
-            @BindsInstance
-            Builder bindTopTaskTracker(TopTaskTracker topTaskTracker);
+            @BindsInstance Builder bindTopTaskTracker(TopTaskTracker topTaskTracker);
 
             @Override
             TopTaskTrackerComponent build();
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index fa81680..be76f9e 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -35,6 +35,9 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.util.AllModulesMinusWMProxy;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.LauncherModelHelper;
@@ -46,6 +49,9 @@
 import com.android.quickstep.FallbackActivityInterface;
 import com.android.quickstep.util.SurfaceTransaction.MockProperties;
 
+import dagger.BindsInstance;
+import dagger.Component;
+
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
 import org.junit.Assert;
@@ -159,6 +165,11 @@
         void verifyNoTransforms() {
             LauncherModelHelper helper = new LauncherModelHelper();
             try {
+                DisplayController mockController = mock(DisplayController.class);
+
+                helper.sandboxContext.initDaggerComponent(
+                        DaggerTaskViewSimulatorTest_TaskViewSimulatorTestComponent.builder()
+                                .bindDisplayController(mockController));
                 int rotation = mDisplaySize.x > mDisplaySize.y
                         ? Surface.ROTATION_90 : Surface.ROTATION_0;
                 CachedDisplayInfo cdi = new CachedDisplayInfo(mDisplaySize, rotation);
@@ -192,10 +203,7 @@
 
                 DisplayController.Info info = new Info(
                         configurationContext, wmProxy, perDisplayBoundsCache);
-
-                DisplayController mockController = mock(DisplayController.class);
                 when(mockController.getInfo()).thenReturn(info);
-                helper.sandboxContext.putObject(DisplayController.INSTANCE, mockController);
 
                 mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(helper.sandboxContext)
                         .getBestMatch(mAppBounds.width(), mAppBounds.height(), rotation);
@@ -271,4 +279,18 @@
             description.appendValue(mExpected);
         }
     }
+
+    @LauncherAppSingleton
+    @Component(modules = {AllModulesMinusWMProxy.class})
+    interface TaskViewSimulatorTestComponent extends LauncherAppComponent {
+
+        @Component.Builder
+        interface Builder extends LauncherAppComponent.Builder {
+
+            @BindsInstance
+            Builder bindDisplayController(DisplayController controller);
+
+            TaskViewSimulatorTestComponent build();
+        }
+    }
 }
diff --git a/src/com/android/launcher3/contextualeducation/ContextualEduStatsManager.java b/src/com/android/launcher3/contextualeducation/ContextualEduStatsManager.java
deleted file mode 100644
index 5664174..0000000
--- a/src/com/android/launcher3/contextualeducation/ContextualEduStatsManager.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 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.contextualeducation;
-
-import com.android.launcher3.dagger.LauncherAppSingleton;
-import com.android.launcher3.dagger.LauncherBaseAppComponent;
-import com.android.launcher3.util.DaggerSingletonObject;
-import com.android.systemui.contextualeducation.GestureType;
-
-import javax.inject.Inject;
-
-/**
- * A class to update contextual education data. It is a no-op implementation and could be
- * overridden through dagger modules to provide a real implementation.
- */
-@LauncherAppSingleton
-public class ContextualEduStatsManager {
-    public static final DaggerSingletonObject<ContextualEduStatsManager> INSTANCE =
-            new DaggerSingletonObject<>(LauncherBaseAppComponent::getContextualEduStatsManager);
-
-    @Inject
-    public ContextualEduStatsManager() { }
-
-
-    /**
-     * Updates contextual education stats when a gesture is triggered
-     * @param isTrackpadGesture indicates if the gesture is triggered by trackpad
-     * @param gestureType type of gesture triggered
-     */
-    public void updateEduStats(boolean isTrackpadGesture, GestureType gestureType) {
-    }
-}
diff --git a/quickstep/dagger/com/android/launcher3/dagger/LauncherAppModule.java b/src/com/android/launcher3/dagger/LauncherAppModule.java
similarity index 79%
rename from quickstep/dagger/com/android/launcher3/dagger/LauncherAppModule.java
rename to src/com/android/launcher3/dagger/LauncherAppModule.java
index 1711fc1..ef136d0 100644
--- a/quickstep/dagger/com/android/launcher3/dagger/LauncherAppModule.java
+++ b/src/com/android/launcher3/dagger/LauncherAppModule.java
@@ -16,9 +16,12 @@
 
 package com.android.launcher3.dagger;
 
-import com.android.quickstep.dagger.QuickStepModule;
-
 import dagger.Module;
 
-@Module(includes = QuickStepModule.class)
-public class LauncherAppModule {}
+@Module(includes = {
+        WindowManagerProxyModule.class,
+        ApiWrapperModule.class,
+        PluginManagerWrapperModule.class
+})
+public class LauncherAppModule {
+}
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 4b43d49..5883a88 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -19,13 +19,13 @@
 import android.content.Context;
 
 import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -50,7 +50,6 @@
 public interface LauncherBaseAppComponent {
     DaggerSingletonTracker getDaggerSingletonTracker();
     ApiWrapper getApiWrapper();
-    ContextualEduStatsManager getContextualEduStatsManager();
     CustomWidgetManager getCustomWidgetManager();
     DynamicResource getDynamicResource();
     IconShape getIconShape();
@@ -66,6 +65,7 @@
     WindowManagerProxy getWmProxy();
     LauncherPrefs getLauncherPrefs();
     ThemeManager getThemeManager();
+    DisplayController getDisplayController();
 
     /** Builder for LauncherBaseAppComponent. */
     interface Builder {
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 74a0966..3817563 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -40,13 +40,11 @@
 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
@@ -390,7 +388,6 @@
         } else {
             logReachedState(mToState);
         }
-        updateContextualEduStats(targetState);
     }
 
     protected void goToTargetState(LauncherState targetState) {
@@ -406,18 +403,6 @@
                 .setDuration(0).start();
     }
 
-    private void updateContextualEduStats(LauncherState targetState) {
-        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/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index d8a2a3d..9472f5f 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -53,6 +53,9 @@
 import com.android.launcher3.LauncherPrefChangeListener;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.util.window.CachedDisplayInfo;
 import com.android.launcher3.util.window.WindowManagerProxy;
@@ -67,11 +70,14 @@
 import java.util.Set;
 import java.util.StringJoiner;
 
+import javax.inject.Inject;
+
 /**
  * Utility class to cache properties of default display to avoid a system RPC on every call.
  */
 @SuppressLint("NewApi")
-public class DisplayController implements ComponentCallbacks, SafeCloseable,
+@LauncherAppSingleton
+public class DisplayController implements ComponentCallbacks,
         DesktopVisibilityListener {
 
     private static final String TAG = "DisplayController";
@@ -82,8 +88,8 @@
     // TODO(b/254119092) remove all logs with this tag
     public static final String TASKBAR_NOT_DESTROYED_TAG = "b/254119092";
 
-    public static final MainThreadInitializedObject<DisplayController> INSTANCE =
-            new MainThreadInitializedObject<>(DisplayController::new);
+    public static final DaggerSingletonObject<DisplayController> INSTANCE =
+            new DaggerSingletonObject<>(LauncherAppComponent::getDisplayController);
 
     public static final int CHANGE_ACTIVE_SCREEN = 1 << 0;
     public static final int CHANGE_ROTATION = 1 << 1;
@@ -101,6 +107,7 @@
     private static final String TARGET_OVERLAY_PACKAGE = "android";
 
     private final Context mContext;
+    private final WindowManagerProxy mWMProxy;
 
     // Null for SDK < S
     private final Context mWindowContext;
@@ -117,13 +124,31 @@
     private Info mInfo;
     private boolean mDestroyed = false;
 
-    private LauncherPrefChangeListener mTaskbarPinningPreferenceChangeListener;
-
-    @VisibleForTesting
-    protected DisplayController(Context context) {
+    @Inject
+    protected DisplayController(@ApplicationContext Context context,
+            WindowManagerProxy wmProxy,
+            LauncherPrefs prefs,
+            DaggerSingletonTracker lifecycle) {
         mContext = context;
+        mWMProxy = wmProxy;
+
         if (enableTaskbarPinning()) {
-            attachTaskbarPinningSharedPreferenceChangeListener(mContext);
+            LauncherPrefChangeListener prefListener = key -> {
+                boolean isTaskbarPinningChanged = TASKBAR_PINNING_KEY.equals(key)
+                        && mInfo.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING);
+                boolean isTaskbarPinningDesktopModeChanged =
+                        TASKBAR_PINNING_DESKTOP_MODE_KEY.equals(key)
+                                && mInfo.mIsTaskbarPinnedInDesktopMode != prefs.get(
+                                TASKBAR_PINNING_IN_DESKTOP_MODE);
+                if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) {
+                    notifyConfigChange();
+                }
+            };
+
+            prefs.addListener(prefListener, TASKBAR_PINNING);
+            prefs.addListener(prefListener, TASKBAR_PINNING_IN_DESKTOP_MODE);
+            lifecycle.addCloseable(() -> prefs.removeListener(
+                        prefListener, TASKBAR_PINNING, TASKBAR_PINNING_IN_DESKTOP_MODE));
         }
 
         Display display = context.getSystemService(DisplayManager.class)
@@ -134,31 +159,17 @@
         // Initialize navigation mode change listener
         mReceiver.registerPkgActions(mContext, TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED);
 
-        WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(context);
         mInfo = new Info(mWindowContext, wmProxy,
                 wmProxy.estimateInternalDisplayBounds(mWindowContext));
         wmProxy.registerDesktopVisibilityListener(this);
         FileLog.i(TAG, "(CTOR) perDisplayBounds: " + mInfo.mPerDisplayBounds);
-    }
 
-    private void attachTaskbarPinningSharedPreferenceChangeListener(Context context) {
-        mTaskbarPinningPreferenceChangeListener = key -> {
-            LauncherPrefs prefs = LauncherPrefs.get(mContext);
-            boolean isTaskbarPinningChanged = TASKBAR_PINNING_KEY.equals(key)
-                    && mInfo.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING);
-            boolean isTaskbarPinningDesktopModeChanged =
-                    TASKBAR_PINNING_DESKTOP_MODE_KEY.equals(key)
-                            && mInfo.mIsTaskbarPinnedInDesktopMode != prefs.get(
-                            TASKBAR_PINNING_IN_DESKTOP_MODE);
-            if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) {
-                notifyConfigChange();
-            }
-        };
-
-        LauncherPrefs.get(context).addListener(
-                mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
-        LauncherPrefs.get(context).addListener(
-                mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING_IN_DESKTOP_MODE);
+        lifecycle.addCloseable(() -> {
+            mDestroyed = true;
+            mWindowContext.unregisterComponentCallbacks(this);
+            mReceiver.unregisterReceiverSafely(mContext);
+            wmProxy.unregisterDesktopVisibilityListener(this);
+        });
     }
 
     /**
@@ -208,20 +219,6 @@
     }
 
     @Override
-    public void close() {
-        mDestroyed = true;
-        if (enableTaskbarPinning()) {
-            LauncherPrefs.get(mContext).removeListener(
-                    mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
-            LauncherPrefs.get(mContext).removeListener(
-                    mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING_IN_DESKTOP_MODE);
-        }
-        mWindowContext.unregisterComponentCallbacks(this);
-        mReceiver.unregisterReceiverSafely(mContext);
-        WindowManagerProxy.INSTANCE.get(mContext).unregisterDesktopVisibilityListener(this);
-    }
-
-    @Override
     public void onDesktopVisibilityChanged(boolean visible) {
         notifyConfigChange();
     }
@@ -259,7 +256,7 @@
                 || !mInfo.mScreenSizeDp.equals(
                         new PortraitSize(config.screenHeightDp, config.screenWidthDp))
                 || mWindowContext.getDisplay().getRotation() != mInfo.rotation
-                || WindowManagerProxy.INSTANCE.get(mContext).showLockedTaskbarOnHome(mWindowContext)
+                || mWMProxy.showLockedTaskbarOnHome(mWindowContext)
                         != mInfo.showLockedTaskbarOnHome()) {
             notifyConfigChange();
         }
@@ -286,17 +283,16 @@
 
     @AnyThread
     public void notifyConfigChange() {
-        WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(mContext);
         Info oldInfo = mInfo;
 
         Context displayInfoContext = mWindowContext;
-        Info newInfo = new Info(displayInfoContext, wmProxy, oldInfo.mPerDisplayBounds);
+        Info newInfo = new Info(displayInfoContext, mWMProxy, oldInfo.mPerDisplayBounds);
 
         if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale
                 || newInfo.getNavigationMode() != oldInfo.getNavigationMode()) {
             // Cache may not be valid anymore, recreate without cache
-            newInfo = new Info(displayInfoContext, wmProxy,
-                    wmProxy.estimateInternalDisplayBounds(displayInfoContext));
+            newInfo = new Info(displayInfoContext, mWMProxy,
+                    mWMProxy.estimateInternalDisplayBounds(displayInfoContext));
         }
 
         int change = 0;
diff --git a/src_no_quickstep/com/android/launcher3/dagger/LauncherAppModule.java b/src_no_quickstep/com/android/launcher3/dagger/LauncherAppModule.java
deleted file mode 100644
index f7b8489..0000000
--- a/src_no_quickstep/com/android/launcher3/dagger/LauncherAppModule.java
+++ /dev/null
@@ -1,22 +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.launcher3.dagger;
-
-import dagger.Module;
-
-@Module
-public class LauncherAppModule { }
diff --git a/quickstep/dagger/com/android/launcher3/dagger/LauncherAppModule.java b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
similarity index 66%
copy from quickstep/dagger/com/android/launcher3/dagger/LauncherAppModule.java
copy to src_no_quickstep/com/android/launcher3/dagger/Modules.kt
index 1711fc1..dab33a0 100644
--- a/quickstep/dagger/com/android/launcher3/dagger/LauncherAppModule.java
+++ b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.dagger;
+package com.android.launcher3.dagger
 
-import com.android.quickstep.dagger.QuickStepModule;
+import dagger.Module
 
-import dagger.Module;
+private object Modules {}
 
-@Module(includes = QuickStepModule.class)
-public class LauncherAppModule {}
+@Module abstract class WindowManagerProxyModule {}
+
+@Module abstract class ApiWrapperModule {}
+
+@Module abstract class PluginManagerWrapperModule {}
diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 6676766..9c64ec9 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -29,9 +29,9 @@
 import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.dagger.LauncherAppComponent
-import com.android.launcher3.dagger.LauncherAppModule
 import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.testing.shared.ResourceUtils
+import com.android.launcher3.util.AllModulesMinusWMProxy
 import com.android.launcher3.util.DisplayController
 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
 import com.android.launcher3.util.NavigationMode
@@ -69,7 +69,7 @@
     protected lateinit var context: SandboxContext
     protected open val runningContext: Context = getApplicationContext()
     private val displayController: DisplayController = mock()
-    private val windowManagerProxy: MyWmProxy = mock()
+    private val windowManagerProxy: WindowManagerProxy = mock()
     private val launcherPrefs: LauncherPrefs = mock()
 
     @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
@@ -312,8 +312,8 @@
             DaggerAbsDPTestSandboxComponent.builder()
                 .bindWMProxy(windowManagerProxy)
                 .bindLauncherPrefs(launcherPrefs)
+                .bindDisplayController(displayController)
         )
-        context.putObject(DisplayController.INSTANCE, displayController)
 
         whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING)).thenReturn(false)
         whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true)
@@ -364,20 +364,18 @@
     }
 }
 
-class MyWmProxy : WindowManagerProxy()
-
 @LauncherAppSingleton
-@Component(modules = [LauncherAppModule::class])
+@Component(modules = [AllModulesMinusWMProxy::class])
 interface AbsDPTestSandboxComponent : LauncherAppComponent {
 
-    override fun getWmProxy(): MyWmProxy
-
     @Component.Builder
     interface Builder : LauncherAppComponent.Builder {
-        @BindsInstance fun bindWMProxy(proxy: MyWmProxy): Builder
+        @BindsInstance fun bindWMProxy(proxy: WindowManagerProxy): Builder
 
         @BindsInstance fun bindLauncherPrefs(prefs: LauncherPrefs): Builder
 
+        @BindsInstance fun bindDisplayController(displayController: DisplayController): Builder
+
         override fun build(): AbsDPTestSandboxComponent
     }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt b/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt
index fcbb94b..97ecafe 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt
@@ -42,10 +42,10 @@
 import com.android.launcher3.LauncherSettings.Favorites.SPANY
 import com.android.launcher3.LauncherSettings.Favorites._ID
 import com.android.launcher3.dagger.LauncherAppComponent
-import com.android.launcher3.dagger.LauncherAppModule
 import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.model.data.AppInfo
 import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.AllModulesMinusApiWrapper
 import com.android.launcher3.util.ApiWrapper
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.LauncherLayoutBuilder
@@ -224,17 +224,15 @@
     }
 }
 
-class MyApiWrapper : ApiWrapper(null) {}
+class MyApiWrapper : ApiWrapper(null)
 
 @LauncherAppSingleton
-@Component(modules = [LauncherAppModule::class])
+@Component(modules = [AllModulesMinusApiWrapper::class])
 interface AutoInstallsLayoutTestComponent : LauncherAppComponent {
 
-    override fun getApiWrapper(): MyApiWrapper
-
     @Component.Builder
     interface Builder : LauncherAppComponent.Builder {
-        @BindsInstance fun bindApiWrapper(wrapper: MyApiWrapper): Builder
+        @BindsInstance fun bindApiWrapper(wrapper: ApiWrapper): Builder
 
         override fun build(): AutoInstallsLayoutTestComponent
     }
diff --git a/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt
index 43bbad9..43b7b68 100644
--- a/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt
@@ -20,9 +20,10 @@
 import androidx.test.filters.SmallTest
 import com.android.launcher3.FakeLauncherPrefs
 import com.android.launcher3.dagger.LauncherAppComponent
-import com.android.launcher3.dagger.LauncherAppModule
 import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.AllModulesForTest
 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.FakePrefsModule
 import com.android.launcher3.util.SandboxApplication
 import com.android.launcher3.util.TestUtil
 import dagger.Component
@@ -91,7 +92,7 @@
 }
 
 @LauncherAppSingleton
-@Component(modules = [LauncherAppModule::class])
+@Component(modules = [AllModulesForTest::class, FakePrefsModule::class])
 interface ThemeManagerComponent : LauncherAppComponent {
 
     override fun getLauncherPrefs(): FakeLauncherPrefs
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
new file mode 100644
index 0000000..68da9ff
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import com.android.launcher3.FakeLauncherPrefs
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.dagger.ApiWrapperModule
+import com.android.launcher3.dagger.WindowManagerProxyModule
+import dagger.Binds
+import dagger.Module
+
+private class DaggerGraphs {}
+
+@Module
+abstract class FakePrefsModule {
+    @Binds abstract fun bindLauncherPrefs(prefs: FakeLauncherPrefs): LauncherPrefs
+}
+
+/** All modules. We also exclude the plugin module from tests */
+@Module(includes = [ApiWrapperModule::class, WindowManagerProxyModule::class])
+class AllModulesForTest
+
+/** All modules except the WMProxy */
+@Module(includes = [ApiWrapperModule::class]) class AllModulesMinusWMProxy
+
+/** All modules except the ApiWrapper */
+@Module(includes = [WindowManagerProxyModule::class]) class AllModulesMinusApiWrapper
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
index e6e156f..7e76e19 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -30,7 +30,6 @@
 import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
 import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE
 import com.android.launcher3.dagger.LauncherAppComponent
-import com.android.launcher3.dagger.LauncherAppModule
 import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
 import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
@@ -43,6 +42,7 @@
 import dagger.Component
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
+import kotlin.math.min
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -57,7 +57,6 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 import org.mockito.stubbing.Answer
-import kotlin.math.min
 
 /** Unit tests for {@link DisplayController} */
 @SmallTest
@@ -139,7 +138,7 @@
         whenever(context.resources).thenReturn(resources)
 
         // Initialize DisplayController
-        displayController = DisplayController(context)
+        displayController = DisplayController.INSTANCE.get(context)
         displayController.addChangeListener(displayInfoChangeListener)
     }
 
@@ -235,14 +234,12 @@
 class MyWmProxy : WindowManagerProxy()
 
 @LauncherAppSingleton
-@Component(modules = [LauncherAppModule::class])
+@Component(modules = [AllModulesMinusWMProxy::class])
 interface DisplayControllerTestComponent : LauncherAppComponent {
 
-    override fun getWmProxy(): MyWmProxy
-
     @Component.Builder
     interface Builder : LauncherAppComponent.Builder {
-        @BindsInstance fun bindWMProxy(proxy: MyWmProxy): Builder
+        @BindsInstance fun bindWMProxy(proxy: WindowManagerProxy): Builder
 
         @BindsInstance fun bindLauncherPrefs(prefs: LauncherPrefs): Builder