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