• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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