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