1 /*
2  * 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.systemui.dagger.SysUISingleton
22 import com.android.systemui.dagger.qualifiers.Application
23 import com.android.systemui.flags.FeatureFlags
24 import com.android.systemui.flags.Flags
25 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
26 import com.android.systemui.keyguard.shared.model.KeyguardState
27 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
28 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
29 import com.android.systemui.keyguard.shared.model.TransitionInfo
30 import com.android.systemui.keyguard.shared.model.TransitionState
31 import com.android.systemui.keyguard.shared.model.WakefulnessState
32 import com.android.systemui.shade.data.repository.ShadeRepository
33 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
34 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
35 import com.android.systemui.util.kotlin.sample
36 import java.util.UUID
37 import javax.inject.Inject
38 import kotlin.time.Duration.Companion.milliseconds
39 import kotlinx.coroutines.CoroutineScope
40 import kotlinx.coroutines.flow.Flow
41 import kotlinx.coroutines.flow.combine
42 import kotlinx.coroutines.flow.distinctUntilChanged
43 import kotlinx.coroutines.flow.map
44 import kotlinx.coroutines.flow.onStart
45 import kotlinx.coroutines.launch
46 
47 @SysUISingleton
48 class FromLockscreenTransitionInteractor
49 @Inject
50 constructor(
51     override val transitionRepository: KeyguardTransitionRepository,
52     override val transitionInteractor: KeyguardTransitionInteractor,
53     @Application private val scope: CoroutineScope,
54     private val keyguardInteractor: KeyguardInteractor,
55     private val flags: FeatureFlags,
56     private val shadeRepository: ShadeRepository,
57 ) :
58     TransitionInteractor(
59         fromState = KeyguardState.LOCKSCREEN,
60     ) {
61 
62     override fun start() {
63         listenForLockscreenToGone()
64         listenForLockscreenToGoneDragging()
65         listenForLockscreenToOccluded()
66         listenForLockscreenToCamera()
67         listenForLockscreenToAodOrDozing()
68         listenForLockscreenToPrimaryBouncer()
69         listenForLockscreenToDreaming()
70         listenForLockscreenToPrimaryBouncerDragging()
71         listenForLockscreenToAlternateBouncer()
72     }
73 
74     /**
75      * Whether we want the surface behind the keyguard visible for the transition from LOCKSCREEN,
76      * or null if we don't care and should just use a reasonable default.
77      *
78      * [KeyguardSurfaceBehindInteractor] will switch to this flow whenever a transition from
79      * LOCKSCREEN is running.
80      */
81     val surfaceBehindVisibility: Flow<Boolean?> =
82         transitionInteractor.startedKeyguardTransitionStep
83             .map { startedStep ->
84                 if (startedStep.to != KeyguardState.GONE) {
85                     // LOCKSCREEN to anything but GONE does not require any special surface
86                     // visibility handling.
87                     return@map null
88                 }
89 
90                 true // TODO(b/278086361): Implement continuous swipe to unlock.
91             }
92             .onStart {
93                 // Default to null ("don't care, use a reasonable default").
94                 emit(null)
95             }
96             .distinctUntilChanged()
97 
98     /**
99      * The surface behind view params to use for the transition from LOCKSCREEN, or null if we don't
100      * care and should use a reasonable default.
101      */
102     val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
103         combine(
104                 transitionInteractor.startedKeyguardTransitionStep,
105                 transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN)
106             ) { startedStep, fromLockscreenStep ->
107                 if (startedStep.to != KeyguardState.GONE) {
108                     // Only LOCKSCREEN -> GONE has specific surface params (for the unlock
109                     // animation).
110                     return@combine null
111                 } else if (fromLockscreenStep.value > 0.5f) {
112                     // Start the animation once we're 50% transitioned to GONE.
113                     KeyguardSurfaceBehindModel(
114                         animateFromAlpha = 0f,
115                         alpha = 1f,
116                         animateFromTranslationY = 500f,
117                         translationY = 0f
118                     )
119                 } else {
120                     KeyguardSurfaceBehindModel(
121                         alpha = 0f,
122                     )
123                 }
124             }
125             .onStart {
126                 // Default to null ("don't care, use a reasonable default").
127                 emit(null)
128             }
129             .distinctUntilChanged()
130 
131     private fun listenForLockscreenToDreaming() {
132         val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
133         scope.launch {
134             keyguardInteractor.isAbleToDream
135                 .sample(
136                     combine(
137                         transitionInteractor.startedKeyguardTransitionStep,
138                         transitionInteractor.finishedKeyguardState,
139                         keyguardInteractor.isActiveDreamLockscreenHosted,
140                         ::Triple
141                     ),
142                     ::toQuad
143                 )
144                 .collect {
145                     (
146                         isAbleToDream,
147                         lastStartedTransition,
148                         finishedKeyguardState,
149                         isActiveDreamLockscreenHosted) ->
150                     val isOnLockscreen = finishedKeyguardState == KeyguardState.LOCKSCREEN
151                     val isTransitionInterruptible =
152                         lastStartedTransition.to == KeyguardState.LOCKSCREEN &&
153                             !invalidFromStates.contains(lastStartedTransition.from)
154                     if (isAbleToDream && (isOnLockscreen || isTransitionInterruptible)) {
155                         if (isActiveDreamLockscreenHosted) {
156                             startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
157                         } else {
158                             startTransitionTo(KeyguardState.DREAMING)
159                         }
160                     }
161                 }
162         }
163     }
164 
165     private fun listenForLockscreenToPrimaryBouncer() {
166         scope.launch {
167             keyguardInteractor.primaryBouncerShowing
168                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
169                 .collect { pair ->
170                     val (isBouncerShowing, lastStartedTransitionStep) = pair
171                     if (
172                         isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
173                     ) {
174                         startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
175                     }
176                 }
177         }
178     }
179 
180     private fun listenForLockscreenToAlternateBouncer() {
181         scope.launch {
182             keyguardInteractor.alternateBouncerShowing
183                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
184                 .collect { pair ->
185                     val (isAlternateBouncerShowing, lastStartedTransitionStep) = pair
186                     if (
187                         isAlternateBouncerShowing &&
188                             lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
189                     ) {
190                         startTransitionTo(KeyguardState.ALTERNATE_BOUNCER)
191                     }
192                 }
193         }
194     }
195 
196     /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
197     private fun listenForLockscreenToPrimaryBouncerDragging() {
198         var transitionId: UUID? = null
199         scope.launch {
200             shadeRepository.shadeModel
201                 .sample(
202                     combine(
203                         transitionInteractor.startedKeyguardTransitionStep,
204                         keyguardInteractor.statusBarState,
205                         keyguardInteractor.isKeyguardUnlocked,
206                         ::Triple
207                     ),
208                     ::toQuad
209                 )
210                 .collect { (shadeModel, keyguardState, statusBarState, isKeyguardUnlocked) ->
211                     val id = transitionId
212                     if (id != null) {
213                         if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) {
214                             // An existing `id` means a transition is started, and calls to
215                             // `updateTransition` will control it until FINISHED or CANCELED
216                             var nextState =
217                                 if (shadeModel.expansionAmount == 0f) {
218                                     TransitionState.FINISHED
219                                 } else if (shadeModel.expansionAmount == 1f) {
220                                     TransitionState.CANCELED
221                                 } else {
222                                     TransitionState.RUNNING
223                                 }
224                             transitionRepository.updateTransition(
225                                 id,
226                                 1f - shadeModel.expansionAmount,
227                                 nextState,
228                             )
229 
230                             if (
231                                 nextState == TransitionState.CANCELED ||
232                                     nextState == TransitionState.FINISHED
233                             ) {
234                                 transitionId = null
235                             }
236 
237                             // If canceled, just put the state back
238                             // TODO(b/278086361): This logic should happen in
239                             //  FromPrimaryBouncerInteractor.
240                             if (nextState == TransitionState.CANCELED) {
241                                 transitionRepository.startTransition(
242                                     TransitionInfo(
243                                         ownerName = name,
244                                         from = KeyguardState.PRIMARY_BOUNCER,
245                                         to = KeyguardState.LOCKSCREEN,
246                                         animator =
247                                             getDefaultAnimatorForTransitionsToState(
248                                                     KeyguardState.LOCKSCREEN
249                                                 )
250                                                 .apply { duration = 0 }
251                                     )
252                                 )
253                             }
254                         }
255                     } else {
256                         // TODO (b/251849525): Remove statusbarstate check when that state is
257                         // integrated into KeyguardTransitionRepository
258                         if (
259                             keyguardState.to == KeyguardState.LOCKSCREEN &&
260                                 shadeModel.isUserDragging &&
261                                 !isKeyguardUnlocked &&
262                                 statusBarState == KEYGUARD
263                         ) {
264                             transitionId = startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
265                         }
266                     }
267                 }
268         }
269     }
270 
271     fun dismissKeyguard() {
272         startTransitionTo(KeyguardState.GONE)
273     }
274 
275     private fun listenForLockscreenToGone() {
276         if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
277             return
278         }
279 
280         scope.launch {
281             keyguardInteractor.isKeyguardGoingAway
282                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
283                 .collect { pair ->
284                     val (isKeyguardGoingAway, lastStartedStep) = pair
285                     if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
286                         startTransitionTo(KeyguardState.GONE)
287                     }
288                 }
289         }
290     }
291 
292     private fun listenForLockscreenToGoneDragging() {
293         if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
294             return
295         }
296 
297         scope.launch {
298             keyguardInteractor.isKeyguardGoingAway
299                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
300                 .collect { pair ->
301                     val (isKeyguardGoingAway, lastStartedStep) = pair
302                     if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
303                         startTransitionTo(KeyguardState.GONE)
304                     }
305                 }
306         }
307     }
308 
309     private fun listenForLockscreenToOccluded() {
310         scope.launch {
311             keyguardInteractor.isKeyguardOccluded
312                 .sample(
313                     combine(
314                         transitionInteractor.finishedKeyguardState,
315                         keyguardInteractor.isDreaming,
316                         ::Pair
317                     ),
318                     ::toTriple
319                 )
320                 .collect { (isOccluded, keyguardState, isDreaming) ->
321                     if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
322                         startTransitionTo(KeyguardState.OCCLUDED)
323                     }
324                 }
325         }
326     }
327 
328     /** This signal may come in before the occlusion signal, and can provide a custom transition */
329     private fun listenForLockscreenToCamera() {
330         scope.launch {
331             keyguardInteractor.onCameraLaunchDetected
332                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
333                 .collect { (_, lastStartedStep) ->
334                     // DREAMING/AOD/OFF may trigger on the first power button push, so include this
335                     // state in order to cancel and correct the transition
336                     if (
337                         lastStartedStep.to == KeyguardState.LOCKSCREEN ||
338                             lastStartedStep.to == KeyguardState.DREAMING ||
339                             lastStartedStep.to == KeyguardState.DOZING ||
340                             lastStartedStep.to == KeyguardState.AOD ||
341                             lastStartedStep.to == KeyguardState.OFF
342                     ) {
343                         startTransitionTo(KeyguardState.OCCLUDED)
344                     }
345                 }
346         }
347     }
348 
349     private fun listenForLockscreenToAodOrDozing() {
350         scope.launch {
351             keyguardInteractor.wakefulnessModel
352                 .sample(
353                     combine(
354                         transitionInteractor.startedKeyguardTransitionStep,
355                         keyguardInteractor.isAodAvailable,
356                         ::Pair
357                     ),
358                     ::toTriple
359                 )
360                 .collect { (wakefulnessState, lastStartedStep, isAodAvailable) ->
361                     if (
362                         lastStartedStep.to == KeyguardState.LOCKSCREEN &&
363                             wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
364                     ) {
365                         startTransitionTo(
366                             if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
367                         )
368                     }
369                 }
370         }
371     }
372 
373     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
374         return ValueAnimator().apply {
375             interpolator = Interpolators.LINEAR
376             duration =
377                 when (toState) {
378                     KeyguardState.DREAMING -> TO_DREAMING_DURATION
379                     KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
380                     else -> DEFAULT_DURATION
381                 }.inWholeMilliseconds
382         }
383     }
384 
385     companion object {
386         private val DEFAULT_DURATION = 400.milliseconds
387         val TO_DREAMING_DURATION = 933.milliseconds
388         val TO_OCCLUDED_DURATION = 450.milliseconds
389     }
390 }
391