1 /* <lambda>null2 * Copyright (C) 2022 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.keyguard.domain.interactor 18 19 import android.animation.ValueAnimator 20 import android.annotation.SuppressLint 21 import android.app.DreamManager 22 import com.android.app.animation.Interpolators 23 import com.android.app.tracing.coroutines.launchTraced as launch 24 import com.android.systemui.communal.domain.interactor.CommunalInteractor 25 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor 26 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor 27 import com.android.systemui.communal.shared.model.CommunalScenes 28 import com.android.systemui.communal.shared.model.CommunalTransitionKeys 29 import com.android.systemui.dagger.SysUISingleton 30 import com.android.systemui.dagger.qualifiers.Background 31 import com.android.systemui.dagger.qualifiers.Main 32 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor 33 import com.android.systemui.keyguard.KeyguardWmStateRefactor 34 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository 35 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode 36 import com.android.systemui.keyguard.shared.model.DozeStateModel 37 import com.android.systemui.keyguard.shared.model.KeyguardState 38 import com.android.systemui.power.domain.interactor.PowerInteractor 39 import com.android.systemui.scene.shared.flag.SceneContainerFlag 40 import com.android.systemui.util.kotlin.sample 41 import javax.inject.Inject 42 import kotlin.time.Duration.Companion.milliseconds 43 import kotlin.time.Duration.Companion.seconds 44 import kotlinx.coroutines.CoroutineDispatcher 45 import kotlinx.coroutines.CoroutineScope 46 import kotlinx.coroutines.FlowPreview 47 import kotlinx.coroutines.flow.combine 48 import kotlinx.coroutines.flow.debounce 49 50 @SysUISingleton 51 class FromDreamingTransitionInteractor 52 @Inject 53 constructor( 54 override val transitionRepository: KeyguardTransitionRepository, 55 override val internalTransitionInteractor: InternalKeyguardTransitionInteractor, 56 transitionInteractor: KeyguardTransitionInteractor, 57 @Background private val scope: CoroutineScope, 58 @Background bgDispatcher: CoroutineDispatcher, 59 @Main mainDispatcher: CoroutineDispatcher, 60 keyguardInteractor: KeyguardInteractor, 61 private val communalInteractor: CommunalInteractor, 62 private val communalSceneInteractor: CommunalSceneInteractor, 63 private val communalSettingsInteractor: CommunalSettingsInteractor, 64 powerInteractor: PowerInteractor, 65 keyguardOcclusionInteractor: KeyguardOcclusionInteractor, 66 private val dreamManager: DreamManager, 67 private val deviceEntryInteractor: DeviceEntryInteractor, 68 ) : 69 TransitionInteractor( 70 fromState = KeyguardState.DREAMING, 71 transitionInteractor = transitionInteractor, 72 mainDispatcher = mainDispatcher, 73 bgDispatcher = bgDispatcher, 74 powerInteractor = powerInteractor, 75 keyguardOcclusionInteractor = keyguardOcclusionInteractor, 76 keyguardInteractor = keyguardInteractor, 77 ) { 78 79 @SuppressLint("MissingPermission") 80 override fun start() { 81 listenForDreamingToAlternateBouncer() 82 listenForDreamingToOccluded() 83 listenForDreamingToGoneWhenDismissable() 84 listenForDreamingToGoneFromBiometricUnlock() 85 listenForDreamingToLockscreenOrGone() 86 listenForDreamingToAodOrDozing() 87 listenForTransitionToCamera(scope, keyguardInteractor) 88 listenForDreamingToGlanceableHubFromPowerButton() 89 listenForDreamingToPrimaryBouncer() 90 } 91 92 private fun listenForDreamingToAlternateBouncer() { 93 scope.launch("$TAG#listenForDreamingToAlternateBouncer") { 94 keyguardInteractor.alternateBouncerShowing 95 .filterRelevantKeyguardStateAnd { isAlternateBouncerShowing -> 96 isAlternateBouncerShowing 97 } 98 .collect { startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) } 99 } 100 } 101 102 /** 103 * Normally when pressing power button from the dream, the devices goes from DREAMING to DOZING, 104 * then [FromDozingTransitionInteractor] handles the transition to GLANCEABLE_HUB. However if 105 * the power button is pressed quickly, we may need to go directly from DREAMING to 106 * GLANCEABLE_HUB as the transition to DOZING has not occurred yet. 107 */ 108 @OptIn(FlowPreview::class) 109 @SuppressLint("MissingPermission") 110 private fun listenForDreamingToGlanceableHubFromPowerButton() { 111 if (!communalSettingsInteractor.isCommunalFlagEnabled()) return 112 if (SceneContainerFlag.isEnabled) return 113 scope.launch { 114 if (communalSettingsInteractor.isV2FlagEnabled()) { 115 powerInteractor.isAwake 116 .debounce(50L) 117 .filterRelevantKeyguardStateAnd { isAwake -> isAwake } 118 .sample(communalSettingsInteractor.autoOpenEnabled) 119 .collect { shouldShowCommunal -> 120 if (shouldShowCommunal) { 121 // This case handles tapping the power button to transition through 122 // dream -> off -> hub. 123 communalSceneInteractor.snapToScene( 124 newScene = CommunalScenes.Communal, 125 loggingReason = "from dreaming to hub", 126 ) 127 } 128 } 129 } else { 130 powerInteractor.isAwake 131 .debounce(50L) 132 .filterRelevantKeyguardStateAnd { isAwake -> isAwake } 133 .sample(communalInteractor.isCommunalAvailable) 134 .collect { isCommunalAvailable -> 135 if (isCommunalAvailable && dreamManager.canStartDreaming(false)) { 136 // This case handles tapping the power button to transition through 137 // dream -> off -> hub. 138 communalSceneInteractor.snapToScene( 139 newScene = CommunalScenes.Communal, 140 loggingReason = "from dreaming to hub", 141 ) 142 } 143 } 144 } 145 } 146 } 147 148 private fun listenForDreamingToPrimaryBouncer() { 149 // TODO(b/336576536): Check if adaptation for scene framework is needed 150 if (SceneContainerFlag.isEnabled) return 151 scope.launch { 152 keyguardInteractor.primaryBouncerShowing 153 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair) 154 .collect { pair -> 155 val (isBouncerShowing, lastStartedTransitionStep) = pair 156 if ( 157 isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.DREAMING 158 ) { 159 startTransitionTo(KeyguardState.PRIMARY_BOUNCER) 160 } 161 } 162 } 163 } 164 165 fun startToLockscreenOrGlanceableHubTransition(openHub: Boolean) { 166 scope.launch { 167 if ( 168 transitionInteractor.startedKeyguardTransitionStep.value.to == 169 KeyguardState.DREAMING 170 ) { 171 if (powerInteractor.detailedWakefulness.value.isAwake()) { 172 if (openHub) { 173 communalSceneInteractor.changeScene( 174 newScene = CommunalScenes.Communal, 175 loggingReason = "FromDreamingTransitionInteractor", 176 transitionKey = 177 if (communalSettingsInteractor.isV2FlagEnabled()) 178 CommunalTransitionKeys.SimpleFade 179 else null, 180 ) 181 } else { 182 startTransitionTo( 183 KeyguardState.LOCKSCREEN, 184 ownerReason = "Dream has ended and device is awake", 185 ) 186 } 187 } 188 } 189 } 190 } 191 192 @OptIn(FlowPreview::class) 193 private fun listenForDreamingToOccluded() { 194 if (!KeyguardWmStateRefactor.isEnabled) { 195 scope.launch { 196 combine( 197 keyguardInteractor.isKeyguardOccluded, 198 keyguardInteractor.isDreaming, 199 ::Pair, 200 ) 201 // Debounce signals since there is a race condition between the occluded and 202 // dreaming signals when starting or stopping dreaming. We therefore add a small 203 // delay to give enough time for occluded to flip to false when the dream 204 // ends, to avoid transitioning to OCCLUDED erroneously when exiting the dream. 205 .debounce(100.milliseconds) 206 .filterRelevantKeyguardStateAnd { (isOccluded, isDreaming) -> 207 isOccluded && !isDreaming 208 } 209 .collect { 210 startTransitionTo( 211 toState = KeyguardState.OCCLUDED, 212 ownerReason = "Occluded but no longer dreaming", 213 ) 214 } 215 } 216 } 217 } 218 219 private fun listenForDreamingToLockscreenOrGone() { 220 if (!KeyguardWmStateRefactor.isEnabled) { 221 return 222 } 223 224 scope.launch { 225 keyguardInteractor.isAbleToDream 226 .filterRelevantKeyguardStateAnd { !it } 227 .sample( 228 if (SceneContainerFlag.isEnabled) { 229 deviceEntryInteractor.isUnlocked 230 } else { 231 keyguardInteractor.isKeyguardDismissible 232 }, 233 ::Pair, 234 ) 235 .collect { (_, dismissable) -> 236 // TODO(b/349837588): Add check for -> OCCLUDED. 237 if (dismissable) { 238 startTransitionTo( 239 KeyguardState.GONE, 240 ownerReason = "No longer dreaming; dismissable", 241 ) 242 } else { 243 startTransitionTo( 244 KeyguardState.LOCKSCREEN, 245 ownerReason = "No longer dreaming", 246 ) 247 } 248 } 249 } 250 } 251 252 private fun listenForDreamingToGoneWhenDismissable() { 253 if (SceneContainerFlag.isEnabled) { 254 return 255 } 256 257 if (KeyguardWmStateRefactor.isEnabled) { 258 return 259 } 260 261 scope.launch { 262 keyguardInteractor.isAbleToDream 263 .filterRelevantKeyguardStateAnd { isDreaming -> !isDreaming } 264 .collect { 265 if ( 266 keyguardInteractor.isKeyguardDismissible.value && 267 !keyguardInteractor.isKeyguardShowing.value 268 ) { 269 startTransitionTo(KeyguardState.GONE) 270 } 271 } 272 } 273 } 274 275 private fun listenForDreamingToGoneFromBiometricUnlock() { 276 // TODO(b/353542570): Adaptation for scene framework is needed 277 if (SceneContainerFlag.isEnabled) return 278 scope.launch { 279 keyguardInteractor.biometricUnlockState 280 .filterRelevantKeyguardStateAnd { biometricUnlockState -> 281 biometricUnlockState.mode == BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM 282 } 283 .collect { startTransitionTo(KeyguardState.GONE) } 284 } 285 } 286 287 private fun listenForDreamingToAodOrDozing() { 288 scope.launch { 289 keyguardInteractor.dozeTransitionModel.filterRelevantKeyguardState().collect { 290 dozeTransitionModel -> 291 if (dozeTransitionModel.to == DozeStateModel.DOZE) { 292 startTransitionTo(KeyguardState.DOZING) 293 } else if (dozeTransitionModel.to == DozeStateModel.DOZE_AOD) { 294 startTransitionTo(KeyguardState.AOD) 295 } 296 } 297 } 298 } 299 300 override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { 301 return ValueAnimator().apply { 302 interpolator = Interpolators.LINEAR 303 duration = 304 when (toState) { 305 KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION 306 KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION 307 else -> DEFAULT_DURATION 308 }.inWholeMilliseconds 309 } 310 } 311 312 companion object { 313 const val TAG = "FromDreamingTransitionInteractor" 314 private val DEFAULT_DURATION = 500.milliseconds 315 val TO_GLANCEABLE_HUB_DURATION = 1.seconds 316 val TO_LOCKSCREEN_DURATION = 1167.milliseconds 317 val TO_AOD_DURATION = 300.milliseconds 318 val TO_GONE_DURATION = DEFAULT_DURATION 319 val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION 320 } 321 } 322