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