Update modules to allow empty impls

This change sets up the ability to replace calls to
CentralSurfacesImpl.getShadeViewController by injecting a
ShadeViewController via Dagger. Now that ShadeViewController is a
singleton it can be injected in the singleton scope, removing the
need for access via CSI. However, variants without a shade cannot
inject SVC without bringing in all of NPVCs dependencies. By
temporarily adding empty implementations of the shade interfaces
and a module to inject those instead, variants will avoid picking
up many unnecessary dependencies during this cleanup work.

Bug: 288868098
Bug: 291620003
Test: updated and ran affected test
Change-Id: If7918ed932a8945d2e014ffd1a741ba93ae17580
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 35cf4a1..3562477 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -43,8 +43,6 @@
 import com.android.systemui.screenshot.ReferenceScreenshotModule;
 import com.android.systemui.settings.dagger.MultiUserUtilsModule;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyboardShortcutsModule;
@@ -150,9 +148,6 @@
     @Binds
     abstract DockManager bindDockManager(DockManagerImpl dockManager);
 
-    @Binds
-    abstract ShadeController provideShadeController(ShadeControllerImpl shadeController);
-
     @SysUISingleton
     @Provides
     @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
index 995c6a4..33c47cc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
@@ -26,7 +26,7 @@
 import javax.inject.Inject
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.shade.ShadeModule.Companion.SHADE_HEADER
+import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import javax.inject.Named
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
new file mode 100644
index 0000000..7a803867
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+
+/** Fulfills dependencies on the shade with empty implementations for variants with no shade. */
+@Module
+abstract class ShadeEmptyImplModule {
+    @Binds
+    @SysUISingleton
+    abstract fun bindsShadeViewController(svc: ShadeViewControllerEmptyImpl): ShadeViewController
+
+    @Binds
+    @SysUISingleton
+    abstract fun bindsShadeController(sc: ShadeControllerEmptyImpl): ShadeController
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 8b89ff4..529f12e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -54,7 +54,7 @@
 import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
 import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
 import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
-import com.android.systemui.shade.ShadeModule.Companion.SHADE_HEADER
+import com.android.systemui.shade.ShadeViewProviderModule.Companion.SHADE_HEADER
 import com.android.systemui.shade.carrier.ShadeCarrierGroup
 import com.android.systemui.shade.carrier.ShadeCarrierGroupController
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index be21df1..89aaaaf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -16,293 +16,21 @@
 
 package com.android.systemui.shade
 
-import android.annotation.SuppressLint
-import android.content.ContentResolver
-import android.os.Handler
-import android.view.LayoutInflater
-import android.view.ViewStub
-import androidx.constraintlayout.motion.widget.MotionLayout
-import com.android.keyguard.LockIconView
-import com.android.systemui.CoreStartable
-import com.android.systemui.R
-import com.android.systemui.battery.BatteryMeterView
-import com.android.systemui.battery.BatteryMeterViewController
-import com.android.systemui.biometrics.AuthRippleController
-import com.android.systemui.biometrics.AuthRippleView
-import com.android.systemui.compose.ComposeFacade
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.ui.view.KeyguardRootView
-import com.android.systemui.privacy.OngoingPrivacyChip
-import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.LightRevealScrim
-import com.android.systemui.statusbar.NotificationShelf
-import com.android.systemui.statusbar.NotificationShelfController
-import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent
-import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
-import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
-import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.phone.StatusIconContainer
-import com.android.systemui.statusbar.phone.TapAgainView
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.tuner.TunerService
+
 import dagger.Binds
 import dagger.Module
-import dagger.Provides
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-import javax.inject.Named
-import javax.inject.Provider
 
 /** Module for classes related to the notification shade. */
-@Module(includes = [StartShadeModule::class])
+@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class])
 abstract class ShadeModule {
-
-    @Binds
-    @IntoMap
-    @ClassKey(AuthRippleController::class)
-    abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable
-
     @Binds
     @SysUISingleton
     abstract fun bindsShadeViewController(
         notificationPanelViewController: NotificationPanelViewController
     ): ShadeViewController
 
-    companion object {
-        const val SHADE_HEADER = "large_screen_shade_header"
-
-        @SuppressLint("InflateParams") // Root views don't have parents.
-        @Provides
-        @SysUISingleton
-        fun providesWindowRootView(
-            layoutInflater: LayoutInflater,
-            featureFlags: FeatureFlags,
-        ): WindowRootView {
-            return if (
-                featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable()
-            ) {
-                layoutInflater.inflate(R.layout.scene_window_root, null)
-            } else {
-                layoutInflater.inflate(R.layout.super_notification_shade, null)
-            }
-                as WindowRootView?
-                ?: throw IllegalStateException("Window root view could not be properly inflated")
-        }
-
-        @Provides
-        @SysUISingleton
-        // TODO(b/277762009): Do something similar to
-        //  {@link StatusBarWindowModule.InternalWindowView} so that only
-        //  {@link NotificationShadeWindowViewController} can inject this view.
-        fun providesNotificationShadeWindowView(
-            root: WindowRootView,
-            featureFlags: FeatureFlags,
-        ): NotificationShadeWindowView {
-            if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
-                return root.findViewById(R.id.legacy_window_root)
-            }
-            return root as NotificationShadeWindowView?
-                ?: throw IllegalStateException("root view not a NotificationShadeWindowView")
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesNotificationStackScrollLayout(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): NotificationStackScrollLayout {
-            return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller)
-        }
-
-        @Provides
-        @SysUISingleton
-        fun providesNotificationShelfController(
-            featureFlags: FeatureFlags,
-            newImpl: Provider<NotificationShelfViewBinderWrapperControllerImpl>,
-            notificationShelfComponentBuilder: NotificationShelfComponent.Builder,
-            layoutInflater: LayoutInflater,
-            notificationStackScrollLayout: NotificationStackScrollLayout,
-        ): NotificationShelfController {
-            return if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
-                newImpl.get()
-            } else {
-                val shelfView =
-                    layoutInflater.inflate(
-                        R.layout.status_bar_notification_shelf,
-                        notificationStackScrollLayout,
-                        false
-                    ) as NotificationShelf
-                val component =
-                    notificationShelfComponentBuilder.notificationShelf(shelfView).build()
-                val notificationShelfController = component.notificationShelfController
-                notificationShelfController.init()
-                notificationShelfController
-            }
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesNotificationPanelView(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): NotificationPanelView {
-            return notificationShadeWindowView.findViewById(R.id.notification_panel)
-        }
-
-        /**
-         * Constructs a new, unattached [KeyguardBottomAreaView].
-         *
-         * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
-         */
-        @Provides
-        fun providesKeyguardBottomAreaView(
-            npv: NotificationPanelView,
-            layoutInflater: LayoutInflater,
-        ): KeyguardBottomAreaView {
-            return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false)
-                as KeyguardBottomAreaView
-        }
-
-        @Provides
-        @SysUISingleton
-        fun providesLightRevealScrim(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): LightRevealScrim {
-            return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim)
-        }
-
-        @Provides
-        @SysUISingleton
-        fun providesKeyguardRootView(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): KeyguardRootView {
-            return notificationShadeWindowView.findViewById(R.id.keyguard_root_view)
-        }
-
-        @Provides
-        @SysUISingleton
-        fun providesSharedNotificationContainer(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): SharedNotificationContainer {
-            return notificationShadeWindowView.findViewById(R.id.shared_notification_container)
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesAuthRippleView(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): AuthRippleView? {
-            return notificationShadeWindowView.findViewById(R.id.auth_ripple)
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesLockIconView(
-            keyguardRootView: KeyguardRootView,
-            notificationPanelView: NotificationPanelView,
-            featureFlags: FeatureFlags
-        ): LockIconView {
-            if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
-                return keyguardRootView.findViewById(R.id.lock_icon_view)
-            } else {
-                return notificationPanelView.findViewById(R.id.lock_icon_view)
-            }
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesTapAgainView(
-            notificationPanelView: NotificationPanelView,
-        ): TapAgainView {
-            return notificationPanelView.findViewById(R.id.shade_falsing_tap_again)
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        fun providesNotificationsQuickSettingsContainer(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): NotificationsQuickSettingsContainer {
-            return notificationShadeWindowView.findViewById(R.id.notification_container_parent)
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesShadeHeaderView(
-            notificationShadeWindowView: NotificationShadeWindowView,
-        ): MotionLayout {
-            val stub = notificationShadeWindowView.findViewById<ViewStub>(R.id.qs_header_stub)
-            val layoutId = R.layout.combined_qs_header
-            stub.layoutResource = layoutId
-            return stub.inflate() as MotionLayout
-        }
-
-        @Provides
-        @SysUISingleton
-        fun providesCombinedShadeHeadersConstraintManager(): CombinedShadeHeadersConstraintManager {
-            return CombinedShadeHeadersConstraintManagerImpl
-        }
-
-        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView {
-            return view.findViewById(R.id.batteryRemainingIcon)
-        }
-
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesBatteryMeterViewController(
-            @Named(SHADE_HEADER) batteryMeterView: BatteryMeterView,
-            userTracker: UserTracker,
-            configurationController: ConfigurationController,
-            tunerService: TunerService,
-            @Main mainHandler: Handler,
-            contentResolver: ContentResolver,
-            batteryController: BatteryController,
-        ): BatteryMeterViewController {
-            return BatteryMeterViewController(
-                batteryMeterView,
-                StatusBarLocation.QS,
-                userTracker,
-                configurationController,
-                tunerService,
-                mainHandler,
-                contentResolver,
-                batteryController,
-            )
-        }
-
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesOngoingPrivacyChip(
-            @Named(SHADE_HEADER) header: MotionLayout,
-        ): OngoingPrivacyChip {
-            return header.findViewById(R.id.privacy_chip)
-        }
-
-        @Provides
-        @SysUISingleton
-        @Named(SHADE_HEADER)
-        fun providesStatusIconContainer(
-            @Named(SHADE_HEADER) header: MotionLayout,
-        ): StatusIconContainer {
-            return header.findViewById(R.id.statusIcons)
-        }
-    }
+    @Binds
+    @SysUISingleton
+    abstract fun bindsShadeController(shadeControllerImpl: ShadeControllerImpl): ShadeController
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
new file mode 100644
index 0000000..fc6479e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.annotation.SuppressLint
+import android.content.ContentResolver
+import android.os.Handler
+import android.view.LayoutInflater
+import android.view.ViewStub
+import androidx.constraintlayout.motion.widget.MotionLayout
+import com.android.keyguard.LockIconView
+import com.android.systemui.R
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.biometrics.AuthRippleView
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.scene.shared.model.SceneContainerConfig
+import com.android.systemui.scene.shared.model.SceneContainerNames
+import com.android.systemui.scene.ui.view.SceneWindowRootView
+import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.phone.TapAgainView
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.tuner.TunerService
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+import javax.inject.Provider
+
+/** Module for providing views related to the shade. */
+@Module
+abstract class ShadeViewProviderModule {
+    companion object {
+        const val SHADE_HEADER = "large_screen_shade_header"
+
+        @SuppressLint("InflateParams") // Root views don't have parents.
+        @Provides
+        @SysUISingleton
+        fun providesWindowRootView(
+            layoutInflater: LayoutInflater,
+            featureFlags: FeatureFlags,
+            @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+            viewModelProvider: Provider<SceneContainerViewModel>,
+            @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+            containerConfigProvider: Provider<SceneContainerConfig>,
+            @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
+            scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
+        ): WindowRootView {
+            return if (
+                featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable()
+            ) {
+                val sceneWindowRootView =
+                    layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
+                sceneWindowRootView.init(
+                    viewModel = viewModelProvider.get(),
+                    containerConfig = containerConfigProvider.get(),
+                    scenes = scenesProvider.get(),
+                )
+                sceneWindowRootView
+            } else {
+                layoutInflater.inflate(R.layout.super_notification_shade, null)
+            }
+                as WindowRootView?
+                ?: throw IllegalStateException("Window root view could not be properly inflated")
+        }
+
+        @Provides
+        @SysUISingleton
+        // TODO(b/277762009): Do something similar to
+        //  {@link StatusBarWindowModule.InternalWindowView} so that only
+        //  {@link NotificationShadeWindowViewController} can inject this view.
+        fun providesNotificationShadeWindowView(
+            root: WindowRootView,
+            featureFlags: FeatureFlags,
+        ): NotificationShadeWindowView {
+            if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
+                return root.findViewById(R.id.legacy_window_root)
+            }
+            return root as NotificationShadeWindowView?
+                ?: throw IllegalStateException("root view not a NotificationShadeWindowView")
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesNotificationStackScrollLayout(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): NotificationStackScrollLayout {
+            return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller)
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesNotificationShelfController(
+            featureFlags: FeatureFlags,
+            newImpl: Provider<NotificationShelfViewBinderWrapperControllerImpl>,
+            notificationShelfComponentBuilder: NotificationShelfComponent.Builder,
+            layoutInflater: LayoutInflater,
+            notificationStackScrollLayout: NotificationStackScrollLayout,
+        ): NotificationShelfController {
+            return if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+                newImpl.get()
+            } else {
+                val shelfView =
+                    layoutInflater.inflate(
+                        R.layout.status_bar_notification_shelf,
+                        notificationStackScrollLayout,
+                        false
+                    ) as NotificationShelf
+                val component =
+                    notificationShelfComponentBuilder.notificationShelf(shelfView).build()
+                val notificationShelfController = component.notificationShelfController
+                notificationShelfController.init()
+                notificationShelfController
+            }
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesNotificationPanelView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): NotificationPanelView {
+            return notificationShadeWindowView.findViewById(R.id.notification_panel)
+        }
+
+        /**
+         * Constructs a new, unattached [KeyguardBottomAreaView].
+         *
+         * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
+         */
+        @Provides
+        fun providesKeyguardBottomAreaView(
+            npv: NotificationPanelView,
+            layoutInflater: LayoutInflater,
+        ): KeyguardBottomAreaView {
+            return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false)
+                as KeyguardBottomAreaView
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesLightRevealScrim(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): LightRevealScrim {
+            return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim)
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesKeyguardRootView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): KeyguardRootView {
+            return notificationShadeWindowView.findViewById(R.id.keyguard_root_view)
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesSharedNotificationContainer(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): SharedNotificationContainer {
+            return notificationShadeWindowView.findViewById(R.id.shared_notification_container)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesAuthRippleView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): AuthRippleView? {
+            return notificationShadeWindowView.findViewById(R.id.auth_ripple)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesLockIconView(
+            keyguardRootView: KeyguardRootView,
+            notificationPanelView: NotificationPanelView,
+            featureFlags: FeatureFlags
+        ): LockIconView {
+            if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+                return keyguardRootView.findViewById(R.id.lock_icon_view)
+            } else {
+                return notificationPanelView.findViewById(R.id.lock_icon_view)
+            }
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesTapAgainView(
+            notificationPanelView: NotificationPanelView,
+        ): TapAgainView {
+            return notificationPanelView.findViewById(R.id.shade_falsing_tap_again)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        fun providesNotificationsQuickSettingsContainer(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): NotificationsQuickSettingsContainer {
+            return notificationShadeWindowView.findViewById(R.id.notification_container_parent)
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesShadeHeaderView(
+            notificationShadeWindowView: NotificationShadeWindowView,
+        ): MotionLayout {
+            val stub = notificationShadeWindowView.findViewById<ViewStub>(R.id.qs_header_stub)
+            val layoutId = R.layout.combined_qs_header
+            stub.layoutResource = layoutId
+            return stub.inflate() as MotionLayout
+        }
+
+        @Provides
+        @SysUISingleton
+        fun providesCombinedShadeHeadersConstraintManager(): CombinedShadeHeadersConstraintManager {
+            return CombinedShadeHeadersConstraintManagerImpl
+        }
+
+        // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView {
+            return view.findViewById(R.id.batteryRemainingIcon)
+        }
+
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesBatteryMeterViewController(
+            @Named(SHADE_HEADER) batteryMeterView: BatteryMeterView,
+            userTracker: UserTracker,
+            configurationController: ConfigurationController,
+            tunerService: TunerService,
+            @Main mainHandler: Handler,
+            contentResolver: ContentResolver,
+            batteryController: BatteryController,
+        ): BatteryMeterViewController {
+            return BatteryMeterViewController(
+                batteryMeterView,
+                StatusBarLocation.QS,
+                userTracker,
+                configurationController,
+                tunerService,
+                mainHandler,
+                contentResolver,
+                batteryController,
+            )
+        }
+
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesOngoingPrivacyChip(
+            @Named(SHADE_HEADER) header: MotionLayout,
+        ): OngoingPrivacyChip {
+            return header.findViewById(R.id.privacy_chip)
+        }
+
+        @Provides
+        @SysUISingleton
+        @Named(SHADE_HEADER)
+        fun providesStatusIconContainer(
+            @Named(SHADE_HEADER) header: MotionLayout,
+        ): StatusIconContainer {
+            return header.findViewById(R.id.statusIcons)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
index c50693c..15ec18c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade
 
 import com.android.systemui.CoreStartable
+import com.android.systemui.biometrics.AuthRippleController
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -28,4 +29,9 @@
     @IntoMap
     @ClassKey(ShadeController::class)
     abstract fun bind(shadeController: ShadeController): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(AuthRippleController::class)
+    abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 8cf71a0..b0e8ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -44,8 +44,7 @@
 import com.android.systemui.screenshot.ReferenceScreenshotModule;
 import com.android.systemui.settings.dagger.MultiUserUtilsModule;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeControllerEmptyImpl;
+import com.android.systemui.shade.ShadeEmptyImplModule;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyboardShortcutsModule;
@@ -95,6 +94,7 @@
                 PowerModule.class,
                 QSModule.class,
                 ReferenceScreenshotModule.class,
+                ShadeEmptyImplModule.class,
                 StatusBarEventsModule.class,
                 VolumeModule.class,
                 KeyboardShortcutsModule.class
@@ -139,9 +139,6 @@
     @Binds
     abstract DockManager bindDockManager(DockManagerImpl dockManager);
 
-    @Binds
-    abstract ShadeController provideShadeController(ShadeControllerEmptyImpl shadeController);
-
     @SysUISingleton
     @Provides
     @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)