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 com.android.app.animation.Interpolators 21 import com.android.app.tracing.coroutines.launch 22 import com.android.systemui.Flags.communalHub 23 import com.android.systemui.dagger.SysUISingleton 24 import com.android.systemui.dagger.qualifiers.Background 25 import com.android.systemui.dagger.qualifiers.Main 26 import com.android.systemui.keyguard.KeyguardWmStateRefactor 27 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository 28 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode 29 import com.android.systemui.keyguard.shared.model.DozeStateModel 30 import com.android.systemui.keyguard.shared.model.KeyguardState 31 import com.android.systemui.power.domain.interactor.PowerInteractor 32 import com.android.systemui.scene.shared.flag.SceneContainerFlag 33 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine 34 import com.android.systemui.util.kotlin.sample 35 import javax.inject.Inject 36 import kotlin.time.Duration.Companion.milliseconds 37 import kotlin.time.Duration.Companion.seconds 38 import kotlinx.coroutines.CoroutineDispatcher 39 import kotlinx.coroutines.CoroutineScope 40 import kotlinx.coroutines.FlowPreview 41 import kotlinx.coroutines.flow.combine 42 import kotlinx.coroutines.flow.debounce 43 import kotlinx.coroutines.launch 44 45 @SysUISingleton 46 class FromDreamingTransitionInteractor 47 @Inject 48 constructor( 49 override val transitionRepository: KeyguardTransitionRepository, 50 transitionInteractor: KeyguardTransitionInteractor, 51 @Background private val scope: CoroutineScope, 52 @Background bgDispatcher: CoroutineDispatcher, 53 @Main mainDispatcher: CoroutineDispatcher, 54 keyguardInteractor: KeyguardInteractor, 55 private val glanceableHubTransitions: GlanceableHubTransitions, 56 powerInteractor: PowerInteractor, 57 keyguardOcclusionInteractor: KeyguardOcclusionInteractor, 58 ) : 59 TransitionInteractor( 60 fromState = KeyguardState.DREAMING, 61 transitionInteractor = transitionInteractor, 62 mainDispatcher = mainDispatcher, 63 bgDispatcher = bgDispatcher, 64 powerInteractor = powerInteractor, 65 keyguardOcclusionInteractor = keyguardOcclusionInteractor, 66 keyguardInteractor = keyguardInteractor, 67 ) { 68 69 override fun start() { 70 listenForDreamingToAlternateBouncer() 71 listenForDreamingToOccluded() 72 listenForDreamingToGoneWhenDismissable() 73 listenForDreamingToGoneFromBiometricUnlock() 74 listenForDreamingToLockscreen() 75 listenForDreamingToAodOrDozing() 76 listenForTransitionToCamera(scope, keyguardInteractor) 77 listenForDreamingToGlanceableHub() 78 listenForDreamingToPrimaryBouncer() 79 } 80 81 private fun listenForDreamingToAlternateBouncer() { 82 scope.launch("$TAG#listenForDreamingToAlternateBouncer") { 83 keyguardInteractor.alternateBouncerShowing 84 .filterRelevantKeyguardStateAnd { isAlternateBouncerShowing -> 85 isAlternateBouncerShowing 86 } 87 .collect { startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) } 88 } 89 } 90 91 private fun listenForDreamingToGlanceableHub() { 92 if (!communalHub()) return 93 if (SceneContainerFlag.isEnabled) 94 return // TODO(b/336576536): Check if adaptation for scene framework is needed 95 scope.launch("$TAG#listenForDreamingToGlanceableHub", mainDispatcher) { 96 glanceableHubTransitions.listenForGlanceableHubTransition( 97 transitionOwnerName = TAG, 98 fromState = KeyguardState.DREAMING, 99 toState = KeyguardState.GLANCEABLE_HUB, 100 ) 101 } 102 } 103 104 private fun listenForDreamingToPrimaryBouncer() { 105 scope.launch { 106 keyguardInteractor.primaryBouncerShowing 107 .sample(startedKeyguardTransitionStep, ::Pair) 108 .collect { pair -> 109 val (isBouncerShowing, lastStartedTransitionStep) = pair 110 if ( 111 isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.DREAMING 112 ) { 113 startTransitionTo(KeyguardState.PRIMARY_BOUNCER) 114 } 115 } 116 } 117 } 118 119 fun startToLockscreenTransition() { 120 scope.launch { 121 if ( 122 transitionInteractor.startedKeyguardState.replayCache.last() == 123 KeyguardState.DREAMING 124 ) { 125 startTransitionTo(KeyguardState.LOCKSCREEN) 126 } 127 } 128 } 129 130 @OptIn(FlowPreview::class) 131 private fun listenForDreamingToOccluded() { 132 if (KeyguardWmStateRefactor.isEnabled) { 133 scope.launch { 134 combine( 135 keyguardInteractor.isDreaming, 136 keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop, 137 ::Pair 138 ) 139 .filterRelevantKeyguardStateAnd { (isDreaming, _) -> !isDreaming } 140 .collect { maybeStartTransitionToOccludedOrInsecureCamera() } 141 } 142 } else { 143 scope.launch { 144 combine( 145 keyguardInteractor.isKeyguardOccluded, 146 keyguardInteractor.isDreaming 147 // Debounce the dreaming signal since there is a race condition between 148 // the occluded and dreaming signals. We therefore add a small delay 149 // to give enough time for occluded to flip to false when the dream 150 // ends, to avoid transitioning to OCCLUDED erroneously when exiting 151 // the dream. 152 .debounce(100.milliseconds), 153 ::Pair 154 ) 155 .filterRelevantKeyguardStateAnd { (isOccluded, isDreaming) -> 156 isOccluded && !isDreaming 157 } 158 .collect { 159 startTransitionTo( 160 toState = KeyguardState.OCCLUDED, 161 ownerReason = "Occluded but no longer dreaming", 162 ) 163 } 164 } 165 } 166 } 167 168 private fun listenForDreamingToLockscreen() { 169 if (!KeyguardWmStateRefactor.isEnabled) { 170 return 171 } 172 173 scope.launch { 174 keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop 175 .filterRelevantKeyguardStateAnd { onTop -> !onTop } 176 .collect { startTransitionTo(KeyguardState.LOCKSCREEN) } 177 } 178 } 179 180 private fun listenForDreamingToGoneWhenDismissable() { 181 if (SceneContainerFlag.isEnabled) 182 return // TODO(b/336576536): Check if adaptation for scene framework is needed 183 scope.launch { 184 keyguardInteractor.isAbleToDream 185 .sampleCombine( 186 keyguardInteractor.isKeyguardShowing, 187 keyguardInteractor.isKeyguardDismissible, 188 ) 189 .filterRelevantKeyguardStateAnd { 190 (isDreaming, isKeyguardShowing, isKeyguardDismissible) -> 191 !isDreaming && isKeyguardDismissible && !isKeyguardShowing 192 } 193 .collect { startTransitionTo(KeyguardState.GONE) } 194 } 195 } 196 197 private fun listenForDreamingToGoneFromBiometricUnlock() { 198 // TODO(b/336576536): Check if adaptation for scene framework is needed 199 if (SceneContainerFlag.isEnabled) return 200 scope.launch { 201 keyguardInteractor.biometricUnlockState 202 .filterRelevantKeyguardStateAnd { biometricUnlockState -> 203 biometricUnlockState.mode == BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM 204 } 205 .collect { startTransitionTo(KeyguardState.GONE) } 206 } 207 } 208 209 private fun listenForDreamingToAodOrDozing() { 210 scope.launch { 211 keyguardInteractor.dozeTransitionModel.filterRelevantKeyguardState().collect { 212 dozeTransitionModel -> 213 if (dozeTransitionModel.to == DozeStateModel.DOZE) { 214 startTransitionTo(KeyguardState.DOZING) 215 } else if (dozeTransitionModel.to == DozeStateModel.DOZE_AOD) { 216 startTransitionTo(KeyguardState.AOD) 217 } 218 } 219 } 220 } 221 222 override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { 223 return ValueAnimator().apply { 224 interpolator = Interpolators.LINEAR 225 duration = 226 when (toState) { 227 KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION 228 KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION 229 else -> DEFAULT_DURATION 230 }.inWholeMilliseconds 231 } 232 } 233 234 companion object { 235 const val TAG = "FromDreamingTransitionInteractor" 236 private val DEFAULT_DURATION = 500.milliseconds 237 val TO_GLANCEABLE_HUB_DURATION = 1.seconds 238 val TO_LOCKSCREEN_DURATION = 1167.milliseconds 239 val TO_AOD_DURATION = 300.milliseconds 240 val TO_GONE_DURATION = DEFAULT_DURATION 241 } 242 } 243