1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.shade
18 
19 import android.annotation.SuppressLint
20 import android.content.ContentResolver
21 import android.os.Handler
22 import android.view.LayoutInflater
23 import android.view.ViewStub
24 import androidx.constraintlayout.motion.widget.MotionLayout
25 import com.android.keyguard.LockIconView
26 import com.android.systemui.R
27 import com.android.systemui.battery.BatteryMeterView
28 import com.android.systemui.battery.BatteryMeterViewController
29 import com.android.systemui.biometrics.AuthRippleView
30 import com.android.systemui.compose.ComposeFacade
31 import com.android.systemui.dagger.SysUISingleton
32 import com.android.systemui.dagger.qualifiers.Main
33 import com.android.systemui.flags.FeatureFlags
34 import com.android.systemui.flags.Flags
35 import com.android.systemui.keyguard.ui.view.KeyguardRootView
36 import com.android.systemui.privacy.OngoingPrivacyChip
37 import com.android.systemui.scene.shared.model.Scene
38 import com.android.systemui.scene.shared.model.SceneContainerConfig
39 import com.android.systemui.scene.ui.view.SceneWindowRootView
40 import com.android.systemui.scene.ui.view.WindowRootView
41 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
42 import com.android.systemui.settings.UserTracker
43 import com.android.systemui.statusbar.LightRevealScrim
44 import com.android.systemui.statusbar.NotificationInsetsController
45 import com.android.systemui.statusbar.NotificationShelf
46 import com.android.systemui.statusbar.NotificationShelfController
47 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent
48 import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
49 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
50 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
51 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
52 import com.android.systemui.statusbar.phone.StatusBarLocation
53 import com.android.systemui.statusbar.phone.StatusIconContainer
54 import com.android.systemui.statusbar.phone.TapAgainView
55 import com.android.systemui.statusbar.policy.BatteryController
56 import com.android.systemui.statusbar.policy.ConfigurationController
57 import com.android.systemui.tuner.TunerService
58 import dagger.Module
59 import dagger.Provides
60 import javax.inject.Named
61 import javax.inject.Provider
62 
63 /** Module for providing views related to the shade. */
64 @Module
65 abstract class ShadeViewProviderModule {
66     companion object {
67         const val SHADE_HEADER = "large_screen_shade_header"
68 
69         @SuppressLint("InflateParams") // Root views don't have parents.
70         @Provides
71         @SysUISingleton
72         fun providesWindowRootView(
73             layoutInflater: LayoutInflater,
74             featureFlags: FeatureFlags,
75             viewModelProvider: Provider<SceneContainerViewModel>,
76             containerConfigProvider: Provider<SceneContainerConfig>,
77             scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
78             layoutInsetController: NotificationInsetsController,
79         ): WindowRootView {
80             return if (
81                 featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable()
82             ) {
83                 val sceneWindowRootView =
84                     layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
85                 sceneWindowRootView.init(
86                     viewModel = viewModelProvider.get(),
87                     containerConfig = containerConfigProvider.get(),
88                     scenes = scenesProvider.get(),
89                     layoutInsetController = layoutInsetController,
90                 )
91                 sceneWindowRootView
92             } else {
93                 layoutInflater.inflate(R.layout.super_notification_shade, null)
94             }
95                 as WindowRootView?
96                 ?: throw IllegalStateException("Window root view could not be properly inflated")
97         }
98 
99         @Provides
100         @SysUISingleton
101         // TODO(b/277762009): Do something similar to
102         //  {@link StatusBarWindowModule.InternalWindowView} so that only
103         //  {@link NotificationShadeWindowViewController} can inject this view.
104         fun providesNotificationShadeWindowView(
105             root: WindowRootView,
106             featureFlags: FeatureFlags,
107         ): NotificationShadeWindowView {
108             if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
109                 return root.requireViewById(R.id.legacy_window_root)
110             }
111             return root as NotificationShadeWindowView?
112                 ?: throw IllegalStateException("root view not a NotificationShadeWindowView")
113         }
114 
115         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
116         @Provides
117         @SysUISingleton
118         fun providesNotificationStackScrollLayout(
119             notificationShadeWindowView: NotificationShadeWindowView,
120         ): NotificationStackScrollLayout {
121             return notificationShadeWindowView.requireViewById(R.id.notification_stack_scroller)
122         }
123 
124         @Provides
125         @SysUISingleton
126         fun providesNotificationShelfController(
127             featureFlags: FeatureFlags,
128             newImpl: Provider<NotificationShelfViewBinderWrapperControllerImpl>,
129             notificationShelfComponentBuilder: NotificationShelfComponent.Builder,
130             layoutInflater: LayoutInflater,
131             notificationStackScrollLayout: NotificationStackScrollLayout,
132         ): NotificationShelfController {
133             return if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
134                 newImpl.get()
135             } else {
136                 val shelfView =
137                     layoutInflater.inflate(
138                         R.layout.status_bar_notification_shelf,
139                         notificationStackScrollLayout,
140                         false
141                     ) as NotificationShelf
142                 val component =
143                     notificationShelfComponentBuilder.notificationShelf(shelfView).build()
144                 val notificationShelfController = component.notificationShelfController
145                 notificationShelfController.init()
146                 notificationShelfController
147             }
148         }
149 
150         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
151         @Provides
152         @SysUISingleton
153         fun providesNotificationPanelView(
154             notificationShadeWindowView: NotificationShadeWindowView,
155         ): NotificationPanelView {
156             return notificationShadeWindowView.requireViewById(R.id.notification_panel)
157         }
158 
159         /**
160          * Constructs a new, unattached [KeyguardBottomAreaView].
161          *
162          * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
163          */
164         @Provides
165         fun providesKeyguardBottomAreaView(
166             npv: NotificationPanelView,
167             layoutInflater: LayoutInflater,
168         ): KeyguardBottomAreaView {
169             return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false)
170                 as KeyguardBottomAreaView
171         }
172 
173         @Provides
174         @SysUISingleton
175         fun providesLightRevealScrim(
176             notificationShadeWindowView: NotificationShadeWindowView,
177         ): LightRevealScrim {
178             return notificationShadeWindowView.requireViewById(R.id.light_reveal_scrim)
179         }
180 
181         @Provides
182         @SysUISingleton
183         fun providesKeyguardRootView(
184             notificationShadeWindowView: NotificationShadeWindowView,
185         ): KeyguardRootView {
186             return notificationShadeWindowView.requireViewById(R.id.keyguard_root_view)
187         }
188 
189         @Provides
190         @SysUISingleton
191         fun providesSharedNotificationContainer(
192             notificationShadeWindowView: NotificationShadeWindowView,
193         ): SharedNotificationContainer {
194             return notificationShadeWindowView.requireViewById(R.id.shared_notification_container)
195         }
196 
197         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
198         @Provides
199         @SysUISingleton
200         fun providesAuthRippleView(
201             notificationShadeWindowView: NotificationShadeWindowView,
202         ): AuthRippleView? {
203             return notificationShadeWindowView.requireViewById(R.id.auth_ripple)
204         }
205 
206         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
207         @Provides
208         @SysUISingleton
209         fun providesLockIconView(
210             keyguardRootView: KeyguardRootView,
211             notificationPanelView: NotificationPanelView,
212             featureFlags: FeatureFlags
213         ): LockIconView {
214             if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
215                 return keyguardRootView.requireViewById(R.id.lock_icon_view)
216             } else {
217                 return notificationPanelView.requireViewById(R.id.lock_icon_view)
218             }
219         }
220 
221         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
222         @Provides
223         @SysUISingleton
224         fun providesTapAgainView(
225             notificationPanelView: NotificationPanelView,
226         ): TapAgainView {
227             return notificationPanelView.requireViewById(R.id.shade_falsing_tap_again)
228         }
229 
230         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
231         @Provides
232         @SysUISingleton
233         fun providesNotificationsQuickSettingsContainer(
234             notificationShadeWindowView: NotificationShadeWindowView,
235         ): NotificationsQuickSettingsContainer {
236             return notificationShadeWindowView.requireViewById(R.id.notification_container_parent)
237         }
238 
239         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
240         @Provides
241         @SysUISingleton
242         @Named(SHADE_HEADER)
243         fun providesShadeHeaderView(
244             notificationShadeWindowView: NotificationShadeWindowView,
245         ): MotionLayout {
246             val stub = notificationShadeWindowView.requireViewById<ViewStub>(R.id.qs_header_stub)
247             val layoutId = R.layout.combined_qs_header
248             stub.layoutResource = layoutId
249             return stub.inflate() as MotionLayout
250         }
251 
252         @Provides
253         @SysUISingleton
254         fun providesCombinedShadeHeadersConstraintManager(): CombinedShadeHeadersConstraintManager {
255             return CombinedShadeHeadersConstraintManagerImpl
256         }
257 
258         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
259         @Provides
260         @SysUISingleton
261         @Named(SHADE_HEADER)
262         fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView {
263             return view.requireViewById(R.id.batteryRemainingIcon)
264         }
265 
266         @Provides
267         @SysUISingleton
268         @Named(SHADE_HEADER)
269         fun providesBatteryMeterViewController(
270             @Named(SHADE_HEADER) batteryMeterView: BatteryMeterView,
271             userTracker: UserTracker,
272             configurationController: ConfigurationController,
273             tunerService: TunerService,
274             @Main mainHandler: Handler,
275             contentResolver: ContentResolver,
276             featureFlags: FeatureFlags,
277             batteryController: BatteryController,
278         ): BatteryMeterViewController {
279             return BatteryMeterViewController(
280                 batteryMeterView,
281                 StatusBarLocation.QS,
282                 userTracker,
283                 configurationController,
284                 tunerService,
285                 mainHandler,
286                 contentResolver,
287                 featureFlags,
288                 batteryController,
289             )
290         }
291 
292         @Provides
293         @SysUISingleton
294         @Named(SHADE_HEADER)
295         fun providesOngoingPrivacyChip(
296             @Named(SHADE_HEADER) header: MotionLayout,
297         ): OngoingPrivacyChip {
298             return header.requireViewById(R.id.privacy_chip)
299         }
300 
301         @Provides
302         @SysUISingleton
303         @Named(SHADE_HEADER)
304         fun providesStatusIconContainer(
305             @Named(SHADE_HEADER) header: MotionLayout,
306         ): StatusIconContainer {
307             return header.requireViewById(R.id.statusIcons)
308         }
309     }
310 }
311