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.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.KeyguardState
27 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
28 import com.android.systemui.util.kotlin.sample
29 import javax.inject.Inject
30 import kotlin.time.Duration.Companion.milliseconds
31 import kotlinx.coroutines.CoroutineScope
32 import kotlinx.coroutines.delay
33 import kotlinx.coroutines.flow.combine
34 import kotlinx.coroutines.flow.onEach
35 import kotlinx.coroutines.launch
36 
37 @SysUISingleton
38 class FromDreamingTransitionInteractor
39 @Inject
40 constructor(
41     override val transitionRepository: KeyguardTransitionRepository,
42     override val transitionInteractor: KeyguardTransitionInteractor,
43     @Application private val scope: CoroutineScope,
44     private val keyguardInteractor: KeyguardInteractor,
45 ) :
46     TransitionInteractor(
47         fromState = KeyguardState.DREAMING,
48     ) {
49 
50     override fun start() {
51         listenForDreamingToOccluded()
52         listenForDreamingToGone()
53         listenForDreamingToDozing()
54     }
55 
56     fun startToLockscreenTransition() {
57         scope.launch {
58             if (transitionInteractor.startedKeyguardState.value == KeyguardState.DREAMING) {
59                 startTransitionTo(KeyguardState.LOCKSCREEN)
60             }
61         }
62     }
63 
64     private fun listenForDreamingToOccluded() {
65         scope.launch {
66             keyguardInteractor.isDreaming
67                 // Add a slight delay, as dreaming and occluded events will arrive with a small gap
68                 // in time. This prevents a transition to OCCLUSION happening prematurely.
69                 .onEach { delay(50) }
70                 .sample(
71                     combine(
72                         keyguardInteractor.isKeyguardOccluded,
73                         transitionInteractor.startedKeyguardTransitionStep,
74                         ::Pair,
75                     ),
76                     ::toTriple
77                 )
78                 .collect { (isDreaming, isOccluded, lastStartedTransition) ->
79                     if (
80                         isOccluded &&
81                             !isDreaming &&
82                             (lastStartedTransition.to == KeyguardState.DREAMING ||
83                                 lastStartedTransition.to == KeyguardState.LOCKSCREEN)
84                     ) {
85                         // At the moment, checking for LOCKSCREEN state above provides a corrective
86                         // action. There's no great signal to determine when the dream is ending
87                         // and a transition to OCCLUDED is beginning directly. For now, the solution
88                         // is DREAMING->LOCKSCREEN->OCCLUDED
89                         startTransitionTo(KeyguardState.OCCLUDED)
90                     }
91                 }
92         }
93     }
94 
95     private fun listenForDreamingToGone() {
96         scope.launch {
97             keyguardInteractor.biometricUnlockState
98                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
99                 .collect { (biometricUnlockState, lastStartedTransitionStep) ->
100                     if (
101                         lastStartedTransitionStep.to == KeyguardState.DREAMING &&
102                             biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
103                     ) {
104                         startTransitionTo(KeyguardState.GONE)
105                     }
106                 }
107         }
108     }
109 
110     private fun listenForDreamingToDozing() {
111         scope.launch {
112             combine(
113                     keyguardInteractor.dozeTransitionModel,
114                     transitionInteractor.finishedKeyguardState,
115                     ::Pair
116                 )
117                 .collect { (dozeTransitionModel, keyguardState) ->
118                     if (
119                         dozeTransitionModel.to == DozeStateModel.DOZE &&
120                             keyguardState == KeyguardState.DREAMING
121                     ) {
122                         startTransitionTo(KeyguardState.DOZING)
123                     }
124                 }
125         }
126     }
127 
128     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
129         return ValueAnimator().apply {
130             interpolator = Interpolators.LINEAR
131             duration =
132                 if (toState == KeyguardState.LOCKSCREEN) TO_LOCKSCREEN_DURATION.inWholeMilliseconds
133                 else DEFAULT_DURATION.inWholeMilliseconds
134         }
135     }
136 
137     companion object {
138         private val DEFAULT_DURATION = 500.milliseconds
139         val TO_LOCKSCREEN_DURATION = 1167.milliseconds
140     }
141 }
142