• 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 com.android.systemui.animation.Interpolators
21 import com.android.systemui.dagger.SysUISingleton
22 import com.android.systemui.dagger.qualifiers.Application
23 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
24 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
25 import com.android.systemui.keyguard.shared.model.DozeStateModel
26 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
27 import com.android.systemui.keyguard.shared.model.KeyguardState
28 import com.android.systemui.keyguard.shared.model.TransitionInfo
29 import com.android.systemui.util.kotlin.sample
30 import javax.inject.Inject
31 import kotlin.time.Duration
32 import kotlin.time.Duration.Companion.milliseconds
33 import kotlinx.coroutines.CoroutineScope
34 import kotlinx.coroutines.delay
35 import kotlinx.coroutines.flow.collect
36 import kotlinx.coroutines.flow.combine
37 import kotlinx.coroutines.flow.onEach
38 import kotlinx.coroutines.launch
39 
40 @SysUISingleton
41 class FromDreamingTransitionInteractor
42 @Inject
43 constructor(
44     @Application private val scope: CoroutineScope,
45     private val keyguardInteractor: KeyguardInteractor,
46     private val keyguardTransitionRepository: KeyguardTransitionRepository,
47     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
48 ) : TransitionInteractor(FromDreamingTransitionInteractor::class.simpleName!!) {
49 
50     override fun start() {
51         listenForDreamingToLockscreen()
52         listenForDreamingToOccluded()
53         listenForDreamingToGone()
54         listenForDreamingToDozing()
55     }
56 
57     private fun listenForDreamingToLockscreen() {
58         scope.launch {
59             keyguardInteractor.isAbleToDream
60                 .sample(
61                     combine(
62                         keyguardInteractor.dozeTransitionModel,
63                         keyguardTransitionInteractor.startedKeyguardTransitionStep,
64                         ::Pair
65                     ),
66                     ::toTriple
67                 )
68                 .collect { (isDreaming, dozeTransitionModel, lastStartedTransition) ->
69                     if (
70                         !isDreaming &&
71                             isDozeOff(dozeTransitionModel.to) &&
72                             lastStartedTransition.to == KeyguardState.DREAMING
73                     ) {
74                         keyguardTransitionRepository.startTransition(
75                             TransitionInfo(
76                                 name,
77                                 KeyguardState.DREAMING,
78                                 KeyguardState.LOCKSCREEN,
79                                 getAnimator(TO_LOCKSCREEN_DURATION),
80                             )
81                         )
82                     }
83                 }
84         }
85     }
86 
87     private fun listenForDreamingToOccluded() {
88         scope.launch {
89             keyguardInteractor.isDreaming
90                 // Add a slight delay, as dreaming and occluded events will arrive with a small gap
91                 // in time. This prevents a transition to OCCLUSION happening prematurely.
92                 .onEach { delay(50) }
93                 .sample(
94                     combine(
95                         keyguardInteractor.isKeyguardOccluded,
96                         keyguardTransitionInteractor.startedKeyguardTransitionStep,
97                         ::Pair,
98                     ),
99                     ::toTriple
100                 )
101                 .collect { (isDreaming, isOccluded, lastStartedTransition) ->
102                     if (
103                         isOccluded &&
104                             !isDreaming &&
105                             (lastStartedTransition.to == KeyguardState.DREAMING ||
106                                 lastStartedTransition.to == KeyguardState.LOCKSCREEN)
107                     ) {
108                         // At the moment, checking for LOCKSCREEN state above provides a corrective
109                         // action. There's no great signal to determine when the dream is ending
110                         // and a transition to OCCLUDED is beginning directly. For now, the solution
111                         // is DREAMING->LOCKSCREEN->OCCLUDED
112                         keyguardTransitionRepository.startTransition(
113                             TransitionInfo(
114                                 name,
115                                 lastStartedTransition.to,
116                                 KeyguardState.OCCLUDED,
117                                 getAnimator(),
118                             )
119                         )
120                     }
121                 }
122         }
123     }
124 
125     private fun listenForDreamingToGone() {
126         scope.launch {
127             keyguardInteractor.biometricUnlockState.collect { biometricUnlockState ->
128                 if (biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM) {
129                     keyguardTransitionRepository.startTransition(
130                         TransitionInfo(
131                             name,
132                             KeyguardState.DREAMING,
133                             KeyguardState.GONE,
134                             getAnimator(),
135                         )
136                     )
137                 }
138             }
139         }
140     }
141 
142     private fun listenForDreamingToDozing() {
143         scope.launch {
144             combine(
145                     keyguardInteractor.dozeTransitionModel,
146                     keyguardTransitionInteractor.finishedKeyguardState,
147                     ::Pair
148                 )
149                 .collect { (dozeTransitionModel, keyguardState) ->
150                     if (
151                         dozeTransitionModel.to == DozeStateModel.DOZE &&
152                             keyguardState == KeyguardState.DREAMING
153                     ) {
154                         keyguardTransitionRepository.startTransition(
155                             TransitionInfo(
156                                 name,
157                                 KeyguardState.DREAMING,
158                                 KeyguardState.DOZING,
159                                 getAnimator(),
160                             )
161                         )
162                     }
163                 }
164         }
165     }
166 
167     private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
168         return ValueAnimator().apply {
169             setInterpolator(Interpolators.LINEAR)
170             setDuration(duration.inWholeMilliseconds)
171         }
172     }
173 
174     companion object {
175         private val DEFAULT_DURATION = 500.milliseconds
176         val TO_LOCKSCREEN_DURATION = 1183.milliseconds
177     }
178 }
179