• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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.tracing.coroutines.launchTraced as launch
21 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
22 import com.android.systemui.communal.domain.interactor.CommunalInteractor
23 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
24 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
25 import com.android.systemui.communal.shared.model.CommunalScenes
26 import com.android.systemui.dagger.SysUISingleton
27 import com.android.systemui.dagger.qualifiers.Background
28 import com.android.systemui.dagger.qualifiers.Main
29 import com.android.systemui.keyguard.KeyguardWmStateRefactor
30 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
31 import com.android.systemui.keyguard.shared.model.Edge
32 import com.android.systemui.keyguard.shared.model.KeyguardState
33 import com.android.systemui.power.domain.interactor.PowerInteractor
34 import com.android.systemui.scene.shared.flag.SceneContainerFlag
35 import com.android.systemui.scene.shared.model.Scenes
36 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
37 import com.android.wm.shell.shared.animation.Interpolators
38 import javax.inject.Inject
39 import kotlin.time.Duration.Companion.milliseconds
40 import kotlinx.coroutines.CoroutineDispatcher
41 import kotlinx.coroutines.CoroutineScope
42 import kotlinx.coroutines.delay
43 import kotlinx.coroutines.flow.Flow
44 import kotlinx.coroutines.flow.distinctUntilChanged
45 import kotlinx.coroutines.flow.drop
46 import kotlinx.coroutines.flow.emptyFlow
47 import kotlinx.coroutines.flow.filter
48 import kotlinx.coroutines.flow.flatMapLatest
49 import kotlinx.coroutines.flow.map
50 import kotlinx.coroutines.flow.merge
51 import kotlinx.coroutines.flow.onEach
52 import kotlinx.coroutines.flow.onStart
53 
54 @SysUISingleton
55 class FromAlternateBouncerTransitionInteractor
56 @Inject
57 constructor(
58     override val transitionRepository: KeyguardTransitionRepository,
59     override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
60     transitionInteractor: KeyguardTransitionInteractor,
61     @Background private val scope: CoroutineScope,
62     @Background bgDispatcher: CoroutineDispatcher,
63     @Main mainDispatcher: CoroutineDispatcher,
64     keyguardInteractor: KeyguardInteractor,
65     private val communalInteractor: CommunalInteractor,
66     private val communalSettingsInteractor: CommunalSettingsInteractor,
67     private val communalSceneInteractor: CommunalSceneInteractor,
68     powerInteractor: PowerInteractor,
69     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
70     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
71 ) :
72     TransitionInteractor(
73         fromState = KeyguardState.ALTERNATE_BOUNCER,
74         transitionInteractor = transitionInteractor,
75         mainDispatcher = mainDispatcher,
76         bgDispatcher = bgDispatcher,
77         powerInteractor = powerInteractor,
78         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
79         keyguardInteractor = keyguardInteractor,
80     ) {
81 
82     override fun start() {
83         listenForAlternateBouncerToGone()
84         listenForAlternateBouncerToLockscreenHubAodOrDozing()
85         listenForAlternateBouncerToPrimaryBouncer()
86         listenForTransitionToCamera(scope, keyguardInteractor)
87     }
88 
89     val surfaceBehindVisibility: Flow<Boolean?> =
90         transitionInteractor
91             .transition(
92                 edge = Edge.create(from = KeyguardState.ALTERNATE_BOUNCER, to = Scenes.Gone),
93                 edgeWithoutSceneContainer =
94                     Edge.create(from = KeyguardState.ALTERNATE_BOUNCER, to = KeyguardState.GONE),
95             )
96             .map {
97                 // The alt bouncer is pretty fast to hide, so start the surface behind animation
98                 // around 30%.
99                 it.value > 0.3f
100             }
101             .onStart<Boolean?> {
102                 // Default to null ("don't care, use a reasonable default").
103                 emit(null)
104             }
105             .distinctUntilChanged()
106 
107     private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() {
108         scope.launch {
109             keyguardInteractor.alternateBouncerShowing
110                 // Add a slight delay, as alternateBouncer and primaryBouncer showing event changes
111                 // will arrive with a small gap in time. This prevents a transition to LOCKSCREEN
112                 // happening prematurely.
113                 // This should eventually be removed in favor of
114                 // [KeyguardTransitionInteractor#startDismissKeyguardTransition]
115                 .onEach { delay(150L) }
116                 .sampleCombine(
117                     keyguardInteractor.primaryBouncerShowing,
118                     powerInteractor.isAwake,
119                     keyguardInteractor.isAodAvailable,
120                     communalSceneInteractor.isIdleOnCommunal,
121                     keyguardInteractor.isDreaming,
122                     keyguardInteractor.isKeyguardOccluded,
123                 )
124                 .filterRelevantKeyguardStateAnd {
125                     (isAlternateBouncerShowing, isPrimaryBouncerShowing, _, _, _, _) ->
126                     !isAlternateBouncerShowing && !isPrimaryBouncerShowing
127                 }
128                 .collect { (_, _, isAwake, isAodAvailable, isIdleOnCommunal, isDreaming, isOccluded)
129                     ->
130                     // When unlocking over glanceable hub to enter edit mode, transitioning directly
131                     // to GONE prevents the lockscreen flash. Let listenForAlternateBouncerToGone
132                     // handle it.
133                     if (communalInteractor.editModeOpen.value) return@collect
134                     val hubV2 = communalSettingsInteractor.isV2FlagEnabled()
135                     val to =
136                         if (!isAwake) {
137                             if (isAodAvailable) {
138                                 KeyguardState.AOD
139                             } else {
140                                 KeyguardState.DOZING
141                             }
142                         } else {
143                             if (!hubV2 && isIdleOnCommunal) {
144                                 if (SceneContainerFlag.isEnabled) return@collect
145                                 KeyguardState.GLANCEABLE_HUB
146                             } else if (isOccluded && !isDreaming) {
147                                 KeyguardState.OCCLUDED
148                             } else if (isDreaming) {
149                                 KeyguardState.DREAMING
150                             } else if (hubV2 && isIdleOnCommunal) {
151                                 if (SceneContainerFlag.isEnabled) return@collect
152                                 KeyguardState.GLANCEABLE_HUB
153                             } else {
154                                 KeyguardState.LOCKSCREEN
155                             }
156                         }
157 
158                     if (hubV2 && to != KeyguardState.GLANCEABLE_HUB && isIdleOnCommunal) {
159                         // If bouncer is showing over the hub, we need to make sure we
160                         // properly dismiss the hub when transitioning away.
161                         communalSceneInteractor.changeScene(
162                             newScene = CommunalScenes.Blank,
163                             loggingReason = "alternate bouncer no longer showing over GH",
164                             keyguardState = to,
165                         )
166                     } else {
167                         startTransitionTo(to)
168                     }
169                 }
170         }
171     }
172 
173     private fun listenForAlternateBouncerToGone() {
174         if (SceneContainerFlag.isEnabled) return
175         if (KeyguardWmStateRefactor.isEnabled) {
176             // Handled via #dismissAlternateBouncer.
177             return
178         }
179 
180         scope.launch {
181             merge(
182                     keyguardInteractor.isKeyguardGoingAway.filter { it }.map {}, // map to Unit
183                     keyguardInteractor.isKeyguardOccluded.flatMapLatest { keyguardOccluded ->
184                         if (keyguardOccluded) {
185                             primaryBouncerInteractor.keyguardAuthenticatedBiometricsHandled
186                                 // drop the initial state
187                                 .drop(1)
188                         } else {
189                             emptyFlow()
190                         }
191                     },
192                 )
193                 .filterRelevantKeyguardState()
194                 .collect { startTransitionTo(KeyguardState.GONE) }
195         }
196     }
197 
198     private fun listenForAlternateBouncerToPrimaryBouncer() {
199         if (SceneContainerFlag.isEnabled) return
200         scope.launch {
201             keyguardInteractor.primaryBouncerShowing
202                 .filterRelevantKeyguardStateAnd { isPrimaryBouncerShowing ->
203                     isPrimaryBouncerShowing
204                 }
205                 .collect { startTransitionTo(KeyguardState.PRIMARY_BOUNCER) }
206         }
207     }
208 
209     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
210         return ValueAnimator().apply {
211             interpolator = Interpolators.LINEAR
212             duration =
213                 when (toState) {
214                     KeyguardState.AOD -> TO_AOD_DURATION
215                     KeyguardState.DOZING -> TO_DOZING_DURATION
216                     KeyguardState.GONE -> TO_GONE_DURATION
217                     KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
218                     KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
219                     KeyguardState.PRIMARY_BOUNCER -> TO_PRIMARY_BOUNCER_DURATION
220                     else -> TRANSITION_DURATION_MS
221                 }.inWholeMilliseconds
222         }
223     }
224 
225     fun dismissAlternateBouncer() {
226         scope.launch { startTransitionTo(KeyguardState.GONE) }
227     }
228 
229     companion object {
230         const val TAG = "FromAlternateBouncerTransitionInteractor"
231         val TRANSITION_DURATION_MS = 300.milliseconds
232         val TO_AOD_DURATION = TRANSITION_DURATION_MS
233         val TO_DOZING_DURATION = TRANSITION_DURATION_MS
234         val TO_GONE_DURATION = 500.milliseconds
235         val TO_LOCKSCREEN_DURATION = 300.milliseconds
236         val TO_OCCLUDED_DURATION = TRANSITION_DURATION_MS
237         val TO_PRIMARY_BOUNCER_DURATION = TRANSITION_DURATION_MS
238     }
239 }
240