• 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.KeyguardState
25 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
26 import com.android.systemui.keyguard.shared.model.TransitionInfo
27 import com.android.systemui.keyguard.shared.model.TransitionState
28 import com.android.systemui.keyguard.shared.model.WakefulnessState
29 import com.android.systemui.shade.data.repository.ShadeRepository
30 import com.android.systemui.util.kotlin.sample
31 import java.util.UUID
32 import javax.inject.Inject
33 import kotlin.time.Duration
34 import kotlin.time.Duration.Companion.milliseconds
35 import kotlinx.coroutines.CoroutineScope
36 import kotlinx.coroutines.flow.combine
37 import kotlinx.coroutines.launch
38 
39 @SysUISingleton
40 class FromLockscreenTransitionInteractor
41 @Inject
42 constructor(
43     @Application private val scope: CoroutineScope,
44     private val keyguardInteractor: KeyguardInteractor,
45     private val shadeRepository: ShadeRepository,
46     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
47     private val keyguardTransitionRepository: KeyguardTransitionRepository,
48 ) : TransitionInteractor(FromLockscreenTransitionInteractor::class.simpleName!!) {
49 
50     override fun start() {
51         listenForLockscreenToGone()
52         listenForLockscreenToOccluded()
53         listenForLockscreenToCamera()
54         listenForLockscreenToAodOrDozing()
55         listenForLockscreenToPrimaryBouncer()
56         listenForLockscreenToDreaming()
57         listenForLockscreenToPrimaryBouncerDragging()
58         listenForLockscreenToAlternateBouncer()
59     }
60 
61     private fun listenForLockscreenToDreaming() {
62         scope.launch {
63             keyguardInteractor.isAbleToDream
64                 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
65                 .collect { pair ->
66                     val (isAbleToDream, lastStartedTransition) = pair
67                     if (
68                         isAbleToDream &&
69                             lastStartedTransition.to == KeyguardState.LOCKSCREEN &&
70                             lastStartedTransition.from != KeyguardState.AOD
71                     ) {
72                         keyguardTransitionRepository.startTransition(
73                             TransitionInfo(
74                                 name,
75                                 KeyguardState.LOCKSCREEN,
76                                 KeyguardState.DREAMING,
77                                 getAnimator(TO_DREAMING_DURATION),
78                             )
79                         )
80                     }
81                 }
82         }
83     }
84 
85     private fun listenForLockscreenToPrimaryBouncer() {
86         scope.launch {
87             keyguardInteractor.primaryBouncerShowing
88                 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
89                 .collect { pair ->
90                     val (isBouncerShowing, lastStartedTransitionStep) = pair
91                     if (
92                         isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
93                     ) {
94                         keyguardTransitionRepository.startTransition(
95                             TransitionInfo(
96                                 ownerName = name,
97                                 from = KeyguardState.LOCKSCREEN,
98                                 to = KeyguardState.PRIMARY_BOUNCER,
99                                 animator = getAnimator(),
100                             )
101                         )
102                     }
103                 }
104         }
105     }
106 
107     private fun listenForLockscreenToAlternateBouncer() {
108         scope.launch {
109             keyguardInteractor.alternateBouncerShowing
110                 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
111                 .collect { pair ->
112                     val (isAlternateBouncerShowing, lastStartedTransitionStep) = pair
113                     if (
114                         isAlternateBouncerShowing &&
115                             lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
116                     ) {
117                         keyguardTransitionRepository.startTransition(
118                             TransitionInfo(
119                                 ownerName = name,
120                                 from = KeyguardState.LOCKSCREEN,
121                                 to = KeyguardState.ALTERNATE_BOUNCER,
122                                 animator = getAnimator(),
123                             )
124                         )
125                     }
126                 }
127         }
128     }
129 
130     /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
131     private fun listenForLockscreenToPrimaryBouncerDragging() {
132         var transitionId: UUID? = null
133         scope.launch {
134             shadeRepository.shadeModel
135                 .sample(
136                     combine(
137                         keyguardTransitionInteractor.startedKeyguardTransitionStep,
138                         keyguardInteractor.statusBarState,
139                         keyguardInteractor.isKeyguardUnlocked,
140                         ::toTriple
141                     ),
142                     ::toQuad
143                 )
144                 .collect { (shadeModel, keyguardState, statusBarState, isKeyguardUnlocked) ->
145                     val id = transitionId
146                     if (id != null) {
147                         if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) {
148                             // An existing `id` means a transition is started, and calls to
149                             // `updateTransition` will control it until FINISHED or CANCELED
150                             var nextState =
151                                 if (shadeModel.expansionAmount == 0f) {
152                                     TransitionState.FINISHED
153                                 } else if (shadeModel.expansionAmount == 1f) {
154                                     TransitionState.CANCELED
155                                 } else {
156                                     TransitionState.RUNNING
157                                 }
158                             keyguardTransitionRepository.updateTransition(
159                                 id,
160                                 1f - shadeModel.expansionAmount,
161                                 nextState,
162                             )
163 
164                             if (
165                                 nextState == TransitionState.CANCELED ||
166                                     nextState == TransitionState.FINISHED
167                             ) {
168                                 transitionId = null
169                             }
170 
171                             // If canceled, just put the state back
172                             if (nextState == TransitionState.CANCELED) {
173                                 keyguardTransitionRepository.startTransition(
174                                     TransitionInfo(
175                                         ownerName = name,
176                                         from = KeyguardState.PRIMARY_BOUNCER,
177                                         to = KeyguardState.LOCKSCREEN,
178                                         animator = getAnimator(0.milliseconds)
179                                     )
180                                 )
181                             }
182                         }
183                     } else {
184                         // TODO (b/251849525): Remove statusbarstate check when that state is
185                         // integrated into KeyguardTransitionRepository
186                         if (
187                             keyguardState.to == KeyguardState.LOCKSCREEN &&
188                                 shadeModel.isUserDragging &&
189                                 !isKeyguardUnlocked &&
190                                 statusBarState == KEYGUARD
191                         ) {
192                             transitionId =
193                                 keyguardTransitionRepository.startTransition(
194                                     TransitionInfo(
195                                         ownerName = name,
196                                         from = KeyguardState.LOCKSCREEN,
197                                         to = KeyguardState.PRIMARY_BOUNCER,
198                                         animator = null,
199                                     )
200                                 )
201                         }
202                     }
203                 }
204         }
205     }
206 
207     private fun listenForLockscreenToGone() {
208         scope.launch {
209             keyguardInteractor.isKeyguardGoingAway
210                 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
211                 .collect { pair ->
212                     val (isKeyguardGoingAway, lastStartedStep) = pair
213                     if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
214                         keyguardTransitionRepository.startTransition(
215                             TransitionInfo(
216                                 name,
217                                 KeyguardState.LOCKSCREEN,
218                                 KeyguardState.GONE,
219                                 getAnimator(),
220                             )
221                         )
222                     }
223                 }
224         }
225     }
226 
227     private fun listenForLockscreenToOccluded() {
228         scope.launch {
229             keyguardInteractor.isKeyguardOccluded
230                 .sample(
231                     combine(
232                         keyguardTransitionInteractor.finishedKeyguardState,
233                         keyguardInteractor.isDreaming,
234                         ::Pair
235                     ),
236                     ::toTriple
237                 )
238                 .collect { (isOccluded, keyguardState, isDreaming) ->
239                     if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
240                         keyguardTransitionRepository.startTransition(
241                             TransitionInfo(
242                                 name,
243                                 keyguardState,
244                                 KeyguardState.OCCLUDED,
245                                 getAnimator(TO_OCCLUDED_DURATION),
246                             )
247                         )
248                     }
249                 }
250         }
251     }
252 
253     /** This signal may come in before the occlusion signal, and can provide a custom transition */
254     private fun listenForLockscreenToCamera() {
255         scope.launch {
256             keyguardInteractor.onCameraLaunchDetected
257                 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
258                 .collect { (_, lastStartedStep) ->
259                     // DREAMING/AOD/OFF may trigger on the first power button push, so include this
260                     // state in order to cancel and correct the transition
261                     if (
262                         lastStartedStep.to == KeyguardState.LOCKSCREEN ||
263                             lastStartedStep.to == KeyguardState.DREAMING ||
264                             lastStartedStep.to == KeyguardState.DOZING ||
265                             lastStartedStep.to == KeyguardState.AOD ||
266                             lastStartedStep.to == KeyguardState.OFF
267                     ) {
268                         keyguardTransitionRepository.startTransition(
269                             TransitionInfo(
270                                 name,
271                                 KeyguardState.LOCKSCREEN,
272                                 KeyguardState.OCCLUDED,
273                                 getAnimator(TO_OCCLUDED_DURATION),
274                             )
275                         )
276                     }
277                 }
278         }
279     }
280 
281     private fun listenForLockscreenToAodOrDozing() {
282         scope.launch {
283             keyguardInteractor.wakefulnessModel
284                 .sample(
285                     combine(
286                         keyguardTransitionInteractor.startedKeyguardTransitionStep,
287                         keyguardInteractor.isAodAvailable,
288                         ::Pair
289                     ),
290                     ::toTriple
291                 )
292                 .collect { (wakefulnessState, lastStartedStep, isAodAvailable) ->
293                     if (
294                         lastStartedStep.to == KeyguardState.LOCKSCREEN &&
295                             wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
296                     ) {
297                         keyguardTransitionRepository.startTransition(
298                             TransitionInfo(
299                                 name,
300                                 KeyguardState.LOCKSCREEN,
301                                 if (isAodAvailable) {
302                                     KeyguardState.AOD
303                                 } else {
304                                     KeyguardState.DOZING
305                                 },
306                                 getAnimator(),
307                             )
308                         )
309                     }
310                 }
311         }
312     }
313 
314     private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
315         return ValueAnimator().apply {
316             setInterpolator(Interpolators.LINEAR)
317             setDuration(duration.inWholeMilliseconds)
318         }
319     }
320 
321     companion object {
322         private val DEFAULT_DURATION = 500.milliseconds
323         val TO_DREAMING_DURATION = 933.milliseconds
324         val TO_OCCLUDED_DURATION = 450.milliseconds
325     }
326 }
327