1 /*
2  * Copyright 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.scene.domain.startable
18 
19 import com.android.systemui.CoreStartable
20 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
21 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.dagger.qualifiers.Application
24 import com.android.systemui.dagger.qualifiers.DisplayId
25 import com.android.systemui.flags.FeatureFlags
26 import com.android.systemui.flags.Flags
27 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
28 import com.android.systemui.keyguard.shared.model.WakefulnessState
29 import com.android.systemui.model.SysUiState
30 import com.android.systemui.model.updateFlags
31 import com.android.systemui.scene.domain.interactor.SceneInteractor
32 import com.android.systemui.scene.shared.logger.SceneLogger
33 import com.android.systemui.scene.shared.model.ObservableTransitionState
34 import com.android.systemui.scene.shared.model.SceneKey
35 import com.android.systemui.scene.shared.model.SceneModel
36 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
37 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
38 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
39 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
40 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
41 import javax.inject.Inject
42 import kotlinx.coroutines.CoroutineScope
43 import kotlinx.coroutines.flow.distinctUntilChanged
44 import kotlinx.coroutines.flow.map
45 import kotlinx.coroutines.flow.mapNotNull
46 import kotlinx.coroutines.launch
47 
48 /**
49  * Hooks up business logic that manipulates the state of the [SceneInteractor] for the system UI
50  * scene container based on state from other systems.
51  */
52 @SysUISingleton
53 class SceneContainerStartable
54 @Inject
55 constructor(
56     @Application private val applicationScope: CoroutineScope,
57     private val sceneInteractor: SceneInteractor,
58     private val authenticationInteractor: AuthenticationInteractor,
59     private val keyguardInteractor: KeyguardInteractor,
60     private val featureFlags: FeatureFlags,
61     private val sysUiState: SysUiState,
62     @DisplayId private val displayId: Int,
63     private val sceneLogger: SceneLogger,
64 ) : CoreStartable {
65 
66     override fun start() {
67         if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
68             sceneLogger.logFrameworkEnabled(isEnabled = true)
69             hydrateVisibility()
70             automaticallySwitchScenes()
71             hydrateSystemUiState()
72         } else {
73             sceneLogger.logFrameworkEnabled(isEnabled = false)
74         }
75     }
76 
77     /** Updates the visibility of the scene container. */
78     private fun hydrateVisibility() {
79         applicationScope.launch {
80             sceneInteractor.transitionState
81                 .mapNotNull { state ->
82                     when (state) {
83                         is ObservableTransitionState.Idle -> {
84                             if (state.scene != SceneKey.Gone) {
85                                 true to "scene is not Gone"
86                             } else {
87                                 false to "scene is Gone"
88                             }
89                         }
90                         is ObservableTransitionState.Transition -> {
91                             if (state.fromScene == SceneKey.Gone) {
92                                 true to "scene transitioning away from Gone"
93                             } else {
94                                 null
95                             }
96                         }
97                     }
98                 }
99                 .distinctUntilChanged()
100                 .collect { (isVisible, loggingReason) ->
101                     sceneInteractor.setVisible(isVisible, loggingReason)
102                 }
103         }
104     }
105 
106     /** Switches between scenes based on ever-changing application state. */
107     private fun automaticallySwitchScenes() {
108         applicationScope.launch {
109             authenticationInteractor.isUnlocked
110                 .mapNotNull { isUnlocked ->
111                     val renderedScenes =
112                         when (val transitionState = sceneInteractor.transitionState.value) {
113                             is ObservableTransitionState.Idle -> setOf(transitionState.scene)
114                             is ObservableTransitionState.Transition ->
115                                 setOf(
116                                     transitionState.progress,
117                                     transitionState.toScene,
118                                 )
119                         }
120                     val isBypassEnabled = authenticationInteractor.isBypassEnabled()
121                     when {
122                         isUnlocked ->
123                             when {
124                                 // When the device becomes unlocked in Bouncer, go to Gone.
125                                 renderedScenes.contains(SceneKey.Bouncer) ->
126                                     SceneKey.Gone to "device unlocked in Bouncer scene"
127 
128                                 // When the device becomes unlocked in Lockscreen, go to Gone if
129                                 // bypass is enabled.
130                                 renderedScenes.contains(SceneKey.Lockscreen) ->
131                                     if (isBypassEnabled) {
132                                         SceneKey.Gone to
133                                             "device unlocked in Lockscreen scene with bypass"
134                                     } else {
135                                         null
136                                     }
137 
138                                 // We got unlocked while on a scene that's not Lockscreen or
139                                 // Bouncer, no need to change scenes.
140                                 else -> null
141                             }
142 
143                         // When the device becomes locked, to Lockscreen.
144                         !isUnlocked ->
145                             when {
146                                 // Already on lockscreen or bouncer, no need to change scenes.
147                                 renderedScenes.contains(SceneKey.Lockscreen) ||
148                                     renderedScenes.contains(SceneKey.Bouncer) -> null
149 
150                                 // We got locked while on a scene that's not Lockscreen or Bouncer,
151                                 // go to Lockscreen.
152                                 else ->
153                                     SceneKey.Lockscreen to
154                                         "device locked in non-Lockscreen and non-Bouncer scene"
155                             }
156                         else -> null
157                     }
158                 }
159                 .collect { (targetSceneKey, loggingReason) ->
160                     switchToScene(
161                         targetSceneKey = targetSceneKey,
162                         loggingReason = loggingReason,
163                     )
164                 }
165         }
166 
167         applicationScope.launch {
168             keyguardInteractor.wakefulnessModel
169                 .map { wakefulnessModel -> wakefulnessModel.state }
170                 .distinctUntilChanged()
171                 .collect { wakefulnessState ->
172                     when (wakefulnessState) {
173                         WakefulnessState.STARTING_TO_SLEEP -> {
174                             switchToScene(
175                                 targetSceneKey = SceneKey.Lockscreen,
176                                 loggingReason = "device is starting to sleep",
177                             )
178                         }
179                         WakefulnessState.STARTING_TO_WAKE -> {
180                             val authMethod = authenticationInteractor.getAuthenticationMethod()
181                             val isUnlocked = authenticationInteractor.isUnlocked.value
182                             when {
183                                 authMethod == AuthenticationMethodModel.None -> {
184                                     switchToScene(
185                                         targetSceneKey = SceneKey.Gone,
186                                         loggingReason =
187                                             "device is starting to wake up while auth method is" +
188                                                 " none",
189                                     )
190                                 }
191                                 authMethod.isSecure && isUnlocked -> {
192                                     switchToScene(
193                                         targetSceneKey = SceneKey.Gone,
194                                         loggingReason =
195                                             "device is starting to wake up while unlocked with a" +
196                                                 " secure auth method",
197                                     )
198                                 }
199                             }
200                         }
201                         else -> Unit
202                     }
203                 }
204         }
205     }
206 
207     /** Keeps [SysUiState] up-to-date */
208     private fun hydrateSystemUiState() {
209         applicationScope.launch {
210             sceneInteractor.transitionState
211                 .mapNotNull { it as? ObservableTransitionState.Idle }
212                 .map { it.scene }
213                 .distinctUntilChanged()
214                 .collect { sceneKey ->
215                     sysUiState.updateFlags(
216                         displayId,
217                         SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to (sceneKey != SceneKey.Gone),
218                         SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to (sceneKey == SceneKey.Shade),
219                         SYSUI_STATE_QUICK_SETTINGS_EXPANDED to (sceneKey == SceneKey.QuickSettings),
220                         SYSUI_STATE_BOUNCER_SHOWING to (sceneKey == SceneKey.Bouncer),
221                         SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to
222                             (sceneKey == SceneKey.Lockscreen),
223                     )
224                 }
225         }
226     }
227 
228     private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
229         sceneInteractor.changeScene(
230             scene = SceneModel(targetSceneKey),
231             loggingReason = loggingReason,
232         )
233     }
234 }
235