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 18 package com.android.systemui.keyguard 19 20 import android.content.res.Configuration 21 import android.view.View 22 import android.view.ViewGroup 23 import com.android.keyguard.KeyguardStatusViewController 24 import com.android.keyguard.dagger.KeyguardStatusViewComponent 25 import com.android.systemui.CoreStartable 26 import com.android.systemui.R 27 import com.android.systemui.dagger.SysUISingleton 28 import com.android.systemui.flags.FeatureFlags 29 import com.android.systemui.flags.Flags 30 import com.android.systemui.keyguard.ui.binder.KeyguardAmbientIndicationAreaViewBinder 31 import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder 32 import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder 33 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder 34 import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder 35 import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder 36 import com.android.systemui.keyguard.ui.view.KeyguardRootView 37 import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener 38 import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel 39 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel 40 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel 41 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel 42 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel 43 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel 44 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel 45 import com.android.systemui.plugins.ActivityStarter 46 import com.android.systemui.plugins.FalsingManager 47 import com.android.systemui.shade.NotificationShadeWindowView 48 import com.android.systemui.statusbar.KeyguardIndicationController 49 import com.android.systemui.statusbar.VibratorHelper 50 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer 51 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder 52 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel 53 import com.android.systemui.statusbar.policy.KeyguardStateController 54 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator 55 import javax.inject.Inject 56 import kotlinx.coroutines.DisposableHandle 57 import kotlinx.coroutines.ExperimentalCoroutinesApi 58 59 /** Binds keyguard views on startup, and also exposes methods to allow rebinding if views change */ 60 @ExperimentalCoroutinesApi 61 @SysUISingleton 62 class KeyguardViewConfigurator 63 @Inject 64 constructor( 65 private val keyguardRootView: KeyguardRootView, 66 private val sharedNotificationContainer: SharedNotificationContainer, 67 private val keyguardRootViewModel: KeyguardRootViewModel, 68 private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel, 69 private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, 70 private val keyguardAmbientIndicationViewModel: KeyguardAmbientIndicationViewModel, 71 private val notificationShadeWindowView: NotificationShadeWindowView, 72 private val featureFlags: FeatureFlags, 73 private val indicationController: KeyguardIndicationController, 74 private val keyguardQuickAffordancesCombinedViewModel: 75 KeyguardQuickAffordancesCombinedViewModel, 76 private val falsingManager: FalsingManager, 77 private val vibratorHelper: VibratorHelper, 78 private val keyguardStateController: KeyguardStateController, 79 private val keyguardSettingsMenuViewModel: KeyguardSettingsMenuViewModel, 80 private val activityStarter: ActivityStarter, 81 private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel, 82 private val chipbarCoordinator: ChipbarCoordinator, 83 private val keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener, 84 private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel, 85 private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, 86 ) : CoreStartable { 87 88 private var rootViewHandle: DisposableHandle? = null 89 private var indicationAreaHandle: DisposableHandle? = null 90 private var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null 91 private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null 92 private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null 93 private var settingsPopupMenuHandle: DisposableHandle? = null 94 private var keyguardStatusViewController: KeyguardStatusViewController? = null 95 96 override fun start() { 97 bindKeyguardRootView() 98 val notificationPanel = 99 notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup 100 unbindKeyguardBottomArea(notificationPanel) 101 bindIndicationArea() 102 bindLockIconView(notificationPanel) 103 bindKeyguardStatusView(notificationPanel) 104 setupNotificationStackScrollLayout(notificationPanel) 105 bindLeftShortcut() 106 bindRightShortcut() 107 bindAmbientIndicationArea() 108 bindSettingsPopupMenu() 109 110 KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) 111 keyguardBlueprintCommandListener.start() 112 } 113 114 fun setupNotificationStackScrollLayout(legacyParent: ViewGroup) { 115 if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { 116 // This moves the existing NSSL view to a different parent, as the controller is a 117 // singleton and recreating it has other bad side effects 118 val nssl = 119 legacyParent.requireViewById<View>(R.id.notification_stack_scroller).also { 120 (it.getParent() as ViewGroup).removeView(it) 121 } 122 sharedNotificationContainer.addNotificationStackScrollLayout(nssl) 123 SharedNotificationContainerBinder.bind( 124 sharedNotificationContainer, 125 sharedNotificationContainerViewModel, 126 ) 127 } 128 } 129 130 override fun onConfigurationChanged(newConfig: Configuration?) { 131 super.onConfigurationChanged(newConfig) 132 leftShortcutHandle?.onConfigurationChanged() 133 rightShortcutHandle?.onConfigurationChanged() 134 ambientIndicationAreaHandle?.onConfigurationChanged() 135 } 136 137 fun bindIndicationArea() { 138 indicationAreaHandle?.dispose() 139 140 if (!featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { 141 keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let { 142 keyguardRootView.removeView(it) 143 } 144 } 145 146 indicationAreaHandle = 147 KeyguardIndicationAreaBinder.bind( 148 notificationShadeWindowView, 149 keyguardIndicationAreaViewModel, 150 keyguardRootViewModel, 151 indicationController, 152 featureFlags, 153 ) 154 } 155 156 private fun bindKeyguardRootView() { 157 rootViewHandle?.dispose() 158 rootViewHandle = 159 KeyguardRootViewBinder.bind( 160 keyguardRootView, 161 keyguardRootViewModel, 162 featureFlags, 163 occludingAppDeviceEntryMessageViewModel, 164 chipbarCoordinator, 165 keyguardStateController, 166 ) 167 } 168 169 private fun bindLockIconView(legacyParent: ViewGroup) { 170 if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { 171 legacyParent.requireViewById<View>(R.id.lock_icon_view).let { 172 legacyParent.removeView(it) 173 } 174 } else { 175 keyguardRootView.findViewById<View?>(R.id.lock_icon_view)?.let { 176 keyguardRootView.removeView(it) 177 } 178 } 179 } 180 181 private fun bindAmbientIndicationArea() { 182 if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { 183 ambientIndicationAreaHandle?.destroy() 184 ambientIndicationAreaHandle = 185 KeyguardAmbientIndicationAreaViewBinder.bind( 186 notificationShadeWindowView, 187 keyguardAmbientIndicationViewModel, 188 keyguardRootViewModel, 189 ) 190 } else { 191 keyguardRootView.findViewById<View?>(R.id.ambient_indication_container)?.let { 192 keyguardRootView.removeView(it) 193 } 194 } 195 } 196 197 private fun bindSettingsPopupMenu() { 198 if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { 199 settingsPopupMenuHandle?.dispose() 200 settingsPopupMenuHandle = 201 KeyguardSettingsViewBinder.bind( 202 keyguardRootView, 203 keyguardSettingsMenuViewModel, 204 vibratorHelper, 205 activityStarter, 206 ) 207 } else { 208 keyguardRootView.findViewById<View?>(R.id.keyguard_settings_button)?.let { 209 keyguardRootView.removeView(it) 210 } 211 } 212 } 213 214 private fun unbindKeyguardBottomArea(legacyParent: ViewGroup) { 215 if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { 216 legacyParent.requireViewById<View>(R.id.keyguard_bottom_area).let { 217 legacyParent.removeView(it) 218 } 219 } 220 } 221 222 private fun bindLeftShortcut() { 223 leftShortcutHandle?.destroy() 224 if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { 225 leftShortcutHandle = 226 KeyguardQuickAffordanceViewBinder.bind( 227 keyguardRootView.requireViewById(R.id.start_button), 228 keyguardQuickAffordancesCombinedViewModel.startButton, 229 keyguardRootViewModel.alpha, 230 falsingManager, 231 vibratorHelper, 232 ) { 233 indicationController.showTransientIndication(it) 234 } 235 } else { 236 keyguardRootView.findViewById<View?>(R.id.start_button)?.let { 237 keyguardRootView.removeView(it) 238 } 239 } 240 } 241 242 private fun bindRightShortcut() { 243 rightShortcutHandle?.destroy() 244 if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { 245 rightShortcutHandle = 246 KeyguardQuickAffordanceViewBinder.bind( 247 keyguardRootView.requireViewById(R.id.end_button), 248 keyguardQuickAffordancesCombinedViewModel.endButton, 249 keyguardRootViewModel.alpha, 250 falsingManager, 251 vibratorHelper, 252 ) { 253 indicationController.showTransientIndication(it) 254 } 255 } else { 256 keyguardRootView.findViewById<View?>(R.id.end_button)?.let { 257 keyguardRootView.removeView(it) 258 } 259 } 260 } 261 262 fun bindKeyguardStatusView(legacyParent: ViewGroup) { 263 // At startup, 2 views with the ID `R.id.keyguard_status_view` will be available. 264 // Disable one of them 265 if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { 266 legacyParent.findViewById<View>(R.id.keyguard_status_view)?.let { 267 legacyParent.removeView(it) 268 } 269 270 val keyguardStatusView = keyguardRootView.addStatusView() 271 val statusViewComponent = keyguardStatusViewComponentFactory.build(keyguardStatusView) 272 val controller = statusViewComponent.getKeyguardStatusViewController() 273 controller.init() 274 keyguardStatusViewController = controller 275 } else { 276 keyguardRootView.findViewById<View?>(R.id.keyguard_status_view)?.let { 277 keyguardRootView.removeView(it) 278 } 279 } 280 } 281 282 /** 283 * Temporary, to allow NotificationPanelViewController to use the same instance while code is 284 * migrated: b/288242803 285 */ 286 fun getKeyguardStatusViewController() = keyguardStatusViewController 287 fun getKeyguardRootView() = keyguardRootView 288 } 289