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