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.keyguard.KeyguardSecurityModel
21 import com.android.keyguard.KeyguardUpdateMonitor
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.dagger.qualifiers.Application
24 import com.android.systemui.flags.FeatureFlags
25 import com.android.systemui.flags.Flags
26 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
27 import com.android.systemui.keyguard.shared.model.KeyguardState
28 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
29 import com.android.systemui.keyguard.shared.model.WakefulnessState
30 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
31 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
32 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
33 import com.android.systemui.util.kotlin.sample
34 import com.android.wm.shell.animation.Interpolators
35 import javax.inject.Inject
36 import kotlin.time.Duration.Companion.milliseconds
37 import kotlinx.coroutines.CoroutineScope
38 import kotlinx.coroutines.flow.Flow
39 import kotlinx.coroutines.flow.combine
40 import kotlinx.coroutines.flow.distinctUntilChanged
41 import kotlinx.coroutines.flow.onStart
42 import kotlinx.coroutines.launch
43 
44 @SysUISingleton
45 class FromPrimaryBouncerTransitionInteractor
46 @Inject
47 constructor(
48     override val transitionRepository: KeyguardTransitionRepository,
49     override val transitionInteractor: KeyguardTransitionInteractor,
50     @Application private val scope: CoroutineScope,
51     private val keyguardInteractor: KeyguardInteractor,
52     private val flags: FeatureFlags,
53     private val keyguardSecurityModel: KeyguardSecurityModel,
54 ) :
55     TransitionInteractor(
56         fromState = KeyguardState.PRIMARY_BOUNCER,
57     ) {
58 
59     override fun start() {
60         listenForPrimaryBouncerToGone()
61         listenForPrimaryBouncerToAodOrDozing()
62         listenForPrimaryBouncerToLockscreenOrOccluded()
63         listenForPrimaryBouncerToDreamingLockscreenHosted()
64     }
65 
66     val surfaceBehindVisibility: Flow<Boolean?> =
67         combine(
68                 transitionInteractor.startedKeyguardTransitionStep,
69                 transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
70             ) { startedStep, fromBouncerStep ->
71                 if (startedStep.to != KeyguardState.GONE) {
72                     return@combine null
73                 }
74 
75                 fromBouncerStep.value > 0.5f
76             }
77             .onStart {
78                 // Default to null ("don't care, use a reasonable default").
79                 emit(null)
80             }
81             .distinctUntilChanged()
82 
83     val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
84         combine(
85                 transitionInteractor.startedKeyguardTransitionStep,
86                 transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
87             ) { startedStep, fromBouncerStep ->
88                 if (startedStep.to != KeyguardState.GONE) {
89                     // BOUNCER to anything but GONE does not require any special surface
90                     // visibility handling.
91                     return@combine null
92                 }
93 
94                 if (fromBouncerStep.value > 0.5f) {
95                     KeyguardSurfaceBehindModel(
96                         animateFromAlpha = 0f,
97                         alpha = 1f,
98                         animateFromTranslationY = 500f,
99                         translationY = 0f,
100                     )
101                 } else {
102                     KeyguardSurfaceBehindModel(
103                         alpha = 0f,
104                     )
105                 }
106             }
107             .onStart {
108                 // Default to null ("don't care, use a reasonable default").
109                 emit(null)
110             }
111             .distinctUntilChanged()
112 
113     fun dismissPrimaryBouncer() {
114         startTransitionTo(KeyguardState.GONE)
115     }
116 
117     private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
118         scope.launch {
119             keyguardInteractor.primaryBouncerShowing
120                 .sample(
121                     combine(
122                         keyguardInteractor.wakefulnessModel,
123                         transitionInteractor.startedKeyguardTransitionStep,
124                         keyguardInteractor.isKeyguardOccluded,
125                         keyguardInteractor.isActiveDreamLockscreenHosted,
126                         ::toQuad
127                     ),
128                     ::toQuint
129                 )
130                 .collect {
131                     (
132                         isBouncerShowing,
133                         wakefulnessState,
134                         lastStartedTransitionStep,
135                         occluded,
136                         isActiveDreamLockscreenHosted) ->
137                     if (
138                         !isBouncerShowing &&
139                             lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
140                             (wakefulnessState.state == WakefulnessState.AWAKE ||
141                                 wakefulnessState.state == WakefulnessState.STARTING_TO_WAKE) &&
142                             !isActiveDreamLockscreenHosted
143                     ) {
144                         startTransitionTo(
145                             if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
146                         )
147                     }
148                 }
149         }
150     }
151 
152     private fun listenForPrimaryBouncerToAodOrDozing() {
153         scope.launch {
154             keyguardInteractor.primaryBouncerShowing
155                 .sample(
156                     combine(
157                         keyguardInteractor.wakefulnessModel,
158                         transitionInteractor.startedKeyguardTransitionStep,
159                         keyguardInteractor.isAodAvailable,
160                         ::Triple
161                     ),
162                     ::toQuad
163                 )
164                 .collect {
165                     (isBouncerShowing, wakefulnessState, lastStartedTransitionStep, isAodAvailable)
166                     ->
167                     if (
168                         !isBouncerShowing &&
169                             lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
170                             (wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
171                                 wakefulnessState.state == WakefulnessState.ASLEEP)
172                     ) {
173                         startTransitionTo(
174                             if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
175                         )
176                     }
177                 }
178         }
179     }
180 
181     private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
182         scope.launch {
183             keyguardInteractor.primaryBouncerShowing
184                     .sample(
185                             combine(
186                                     keyguardInteractor.isActiveDreamLockscreenHosted,
187                                     transitionInteractor.startedKeyguardTransitionStep,
188                                     ::Pair
189                             ),
190                             ::toTriple
191                     )
192                     .collect { (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
193                         if (
194                                 !isBouncerShowing &&
195                                 isActiveDreamLockscreenHosted &&
196                                 lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
197                         ) {
198                             startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
199                         }
200                     }
201         }
202     }
203 
204     private fun listenForPrimaryBouncerToGone() {
205         if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
206             // This is handled in KeyguardSecurityContainerController and
207             // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
208             // transition vs. listening to legacy state flags.
209             return
210         }
211 
212         scope.launch {
213             keyguardInteractor.isKeyguardGoingAway
214                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
215                 .collect { (isKeyguardGoingAway, lastStartedTransitionStep) ->
216                     if (
217                         isKeyguardGoingAway &&
218                             lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
219                     ) {
220                         val securityMode =
221                             keyguardSecurityModel.getSecurityMode(
222                                 KeyguardUpdateMonitor.getCurrentUser()
223                             )
224                         // IME for password requires a slightly faster animation
225                         val duration =
226                             if (securityMode == KeyguardSecurityModel.SecurityMode.Password) {
227                                 TO_GONE_SHORT_DURATION
228                             } else {
229                                 TO_GONE_DURATION
230                             }
231 
232                         startTransitionTo(
233                             toState = KeyguardState.GONE,
234                             animator =
235                                 getDefaultAnimatorForTransitionsToState(KeyguardState.GONE).apply {
236                                     this.duration = duration.inWholeMilliseconds
237                                 },
238                             resetIfCancelled = true
239                         )
240                     }
241                 }
242         }
243     }
244 
245     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
246         return ValueAnimator().apply {
247             interpolator = Interpolators.LINEAR
248             duration = DEFAULT_DURATION.inWholeMilliseconds
249         }
250     }
251 
252     companion object {
253         private val DEFAULT_DURATION = 300.milliseconds
254         val TO_GONE_DURATION = 500.milliseconds
255         val TO_GONE_SHORT_DURATION = 200.milliseconds
256     }
257 }
258