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