1 /*
<lambda>null2  * 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.compose.animation.scene.SceneKey
26 import com.android.keyguard.logging.ScrimLogger
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.dagger.SysUISingleton
31 import com.android.systemui.dagger.qualifiers.Main
32 import com.android.systemui.flags.FeatureFlags
33 import com.android.systemui.keyguard.ui.view.KeyguardRootView
34 import com.android.systemui.privacy.OngoingPrivacyChip
35 import com.android.systemui.res.R
36 import com.android.systemui.scene.shared.flag.SceneContainerFlag
37 import com.android.systemui.scene.shared.model.Scene
38 import com.android.systemui.scene.shared.model.SceneContainerConfig
39 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
40 import com.android.systemui.scene.ui.view.SceneWindowRootView
41 import com.android.systemui.scene.ui.view.WindowRootView
42 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
43 import com.android.systemui.settings.UserTracker
44 import com.android.systemui.statusbar.LightRevealScrim
45 import com.android.systemui.statusbar.NotificationInsetsController
46 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
47 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
48 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
49 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
50 import com.android.systemui.statusbar.phone.StatusBarLocation
51 import com.android.systemui.statusbar.phone.StatusIconContainer
52 import com.android.systemui.statusbar.phone.TapAgainView
53 import com.android.systemui.statusbar.policy.BatteryController
54 import com.android.systemui.statusbar.policy.ConfigurationController
55 import com.android.systemui.tuner.TunerService
56 import dagger.Binds
57 import dagger.Module
58 import dagger.Provides
59 import javax.inject.Named
60 import javax.inject.Provider
61 
62 /** Module for providing views related to the shade. */
63 @Module
64 abstract class ShadeViewProviderModule {
65 
66     @Binds
67     @SysUISingleton
68     // TODO(b/277762009): Only allow this view's binder to inject the view.
69     abstract fun bindsNotificationScrollView(
70         notificationStackScrollLayout: NotificationStackScrollLayout
71     ): NotificationScrollView
72 
73     companion object {
74         const val SHADE_HEADER = "large_screen_shade_header"
75 
76         @SuppressLint("InflateParams") // Root views don't have parents.
77         @Provides
78         @SysUISingleton
79         fun providesWindowRootView(
80             layoutInflater: LayoutInflater,
81             viewModelProvider: Provider<SceneContainerViewModel>,
82             containerConfigProvider: Provider<SceneContainerConfig>,
83             scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
84             layoutInsetController: NotificationInsetsController,
85             sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>,
86         ): WindowRootView {
87             return if (SceneContainerFlag.isEnabled) {
88                 checkNoSceneDuplicates(scenesProvider.get())
89                 val sceneWindowRootView =
90                     layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
91                 sceneWindowRootView.init(
92                     viewModel = viewModelProvider.get(),
93                     containerConfig = containerConfigProvider.get(),
94                     sharedNotificationContainer =
95                         sceneWindowRootView.requireViewById(R.id.shared_notification_container),
96                     scenes = scenesProvider.get(),
97                     layoutInsetController = layoutInsetController,
98                     sceneDataSourceDelegator = sceneDataSourceDelegator.get(),
99                 )
100                 sceneWindowRootView
101             } else {
102                 layoutInflater.inflate(R.layout.super_notification_shade, null)
103             }
104                 as WindowRootView?
105                 ?: throw IllegalStateException("Window root view could not be properly inflated")
106         }
107 
108         // TODO(b/277762009): Do something similar to
109         //  {@link StatusBarWindowModule.InternalWindowView} so that only
110         //  {@link NotificationShadeWindowViewController} can inject this view.
111         @Provides
112         @SysUISingleton
113         fun providesNotificationShadeWindowView(
114             root: WindowRootView,
115         ): NotificationShadeWindowView {
116             if (SceneContainerFlag.isEnabled) {
117                 return root.requireViewById(R.id.legacy_window_root)
118             }
119             return root as NotificationShadeWindowView?
120                 ?: throw IllegalStateException("root view not a NotificationShadeWindowView")
121         }
122 
123         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
124         @Provides
125         @SysUISingleton
126         fun providesNotificationStackScrollLayout(
127             notificationShadeWindowView: NotificationShadeWindowView,
128         ): NotificationStackScrollLayout {
129             return notificationShadeWindowView.requireViewById(R.id.notification_stack_scroller)
130         }
131 
132         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
133         @Provides
134         @SysUISingleton
135         fun providesNotificationPanelView(
136             notificationShadeWindowView: NotificationShadeWindowView,
137         ): NotificationPanelView {
138             return notificationShadeWindowView.requireViewById(R.id.notification_panel)
139         }
140 
141         /**
142          * Constructs a new, unattached [KeyguardBottomAreaView].
143          *
144          * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
145          */
146         @Provides
147         fun providesKeyguardBottomAreaView(
148             npv: NotificationPanelView,
149             layoutInflater: LayoutInflater,
150         ): KeyguardBottomAreaView {
151             return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false)
152                 as KeyguardBottomAreaView
153         }
154 
155         @Provides
156         @SysUISingleton
157         fun providesLightRevealScrim(
158             notificationShadeWindowView: NotificationShadeWindowView,
159             scrimLogger: ScrimLogger,
160         ): LightRevealScrim {
161             val scrim =
162                 notificationShadeWindowView.requireViewById<LightRevealScrim>(
163                     R.id.light_reveal_scrim
164                 )
165             scrim.scrimLogger = scrimLogger
166             return scrim
167         }
168 
169         @Provides
170         @SysUISingleton
171         fun providesKeyguardRootView(
172             notificationShadeWindowView: NotificationShadeWindowView,
173         ): KeyguardRootView {
174             return notificationShadeWindowView.requireViewById(R.id.keyguard_root_view)
175         }
176 
177         @Provides
178         @SysUISingleton
179         fun providesSharedNotificationContainer(
180             notificationShadeWindowView: NotificationShadeWindowView,
181         ): SharedNotificationContainer {
182             return notificationShadeWindowView.requireViewById(R.id.shared_notification_container)
183         }
184 
185         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
186         @Provides
187         @SysUISingleton
188         fun providesAuthRippleView(
189             notificationShadeWindowView: NotificationShadeWindowView,
190         ): AuthRippleView? {
191             return notificationShadeWindowView.requireViewById(R.id.auth_ripple)
192         }
193 
194         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
195         @Provides
196         @SysUISingleton
197         fun providesTapAgainView(
198             notificationPanelView: NotificationPanelView,
199         ): TapAgainView {
200             return notificationPanelView.requireViewById(R.id.shade_falsing_tap_again)
201         }
202 
203         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
204         @Provides
205         @SysUISingleton
206         fun providesNotificationsQuickSettingsContainer(
207             notificationShadeWindowView: NotificationShadeWindowView,
208         ): NotificationsQuickSettingsContainer {
209             return notificationShadeWindowView.requireViewById(R.id.notification_container_parent)
210         }
211 
212         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
213         @Provides
214         @SysUISingleton
215         @Named(SHADE_HEADER)
216         fun providesShadeHeaderView(
217             notificationShadeWindowView: NotificationShadeWindowView,
218         ): MotionLayout {
219             val stub = notificationShadeWindowView.requireViewById<ViewStub>(R.id.qs_header_stub)
220             val layoutId = R.layout.combined_qs_header
221             stub.layoutResource = layoutId
222             return stub.inflate() as MotionLayout
223         }
224 
225         @Provides
226         @SysUISingleton
227         fun providesCombinedShadeHeadersConstraintManager(): CombinedShadeHeadersConstraintManager {
228             return CombinedShadeHeadersConstraintManagerImpl
229         }
230 
231         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
232         @Provides
233         @SysUISingleton
234         @Named(SHADE_HEADER)
235         fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView {
236             return view.requireViewById(R.id.batteryRemainingIcon)
237         }
238 
239         @Provides
240         @SysUISingleton
241         @Named(SHADE_HEADER)
242         fun providesBatteryMeterViewController(
243             @Named(SHADE_HEADER) batteryMeterView: BatteryMeterView,
244             userTracker: UserTracker,
245             configurationController: ConfigurationController,
246             tunerService: TunerService,
247             @Main mainHandler: Handler,
248             contentResolver: ContentResolver,
249             featureFlags: FeatureFlags,
250             batteryController: BatteryController,
251         ): BatteryMeterViewController {
252             return BatteryMeterViewController(
253                 batteryMeterView,
254                 StatusBarLocation.QS,
255                 userTracker,
256                 configurationController,
257                 tunerService,
258                 mainHandler,
259                 contentResolver,
260                 featureFlags,
261                 batteryController,
262             )
263         }
264 
265         @Provides
266         @SysUISingleton
267         @Named(SHADE_HEADER)
268         fun providesOngoingPrivacyChip(
269             @Named(SHADE_HEADER) header: MotionLayout,
270         ): OngoingPrivacyChip {
271             return header.requireViewById(R.id.privacy_chip)
272         }
273 
274         @Provides
275         @SysUISingleton
276         @Named(SHADE_HEADER)
277         fun providesStatusIconContainer(
278             @Named(SHADE_HEADER) header: MotionLayout,
279         ): StatusIconContainer {
280             return header.requireViewById(R.id.statusIcons)
281         }
282 
283         private fun checkNoSceneDuplicates(scenes: Set<Scene>) {
284             val keys = mutableSetOf<SceneKey>()
285             val duplicates = mutableSetOf<SceneKey>()
286             scenes
287                 .map { it.key }
288                 .forEach { sceneKey ->
289                     if (keys.contains(sceneKey)) {
290                         duplicates.add(sceneKey)
291                     } else {
292                         keys.add(sceneKey)
293                     }
294                 }
295 
296             check(duplicates.isEmpty()) { "Duplicate scenes detected: $duplicates" }
297         }
298     }
299 }
300