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 android.util.MathUtils
21 import com.android.app.animation.Interpolators
22 import com.android.app.tracing.coroutines.launch
23 import com.android.systemui.Flags
24 import com.android.systemui.dagger.SysUISingleton
25 import com.android.systemui.dagger.qualifiers.Background
26 import com.android.systemui.dagger.qualifiers.Main
27 import com.android.systemui.keyguard.KeyguardWmStateRefactor
28 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
29 import com.android.systemui.keyguard.shared.model.Edge
30 import com.android.systemui.keyguard.shared.model.KeyguardState
31 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
32 import com.android.systemui.keyguard.shared.model.TransitionInfo
33 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
34 import com.android.systemui.keyguard.shared.model.TransitionState
35 import com.android.systemui.keyguard.shared.model.TransitionStep
36 import com.android.systemui.power.domain.interactor.PowerInteractor
37 import com.android.systemui.scene.shared.flag.SceneContainerFlag
38 import com.android.systemui.scene.shared.model.Scenes
39 import com.android.systemui.shade.data.repository.ShadeRepository
40 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
41 import java.util.UUID
42 import javax.inject.Inject
43 import kotlin.time.Duration.Companion.milliseconds
44 import kotlin.time.Duration.Companion.seconds
45 import kotlinx.coroutines.CoroutineDispatcher
46 import kotlinx.coroutines.CoroutineScope
47 import kotlinx.coroutines.flow.Flow
48 import kotlinx.coroutines.flow.distinctUntilChanged
49 import kotlinx.coroutines.flow.filterNotNull
50 import kotlinx.coroutines.flow.map
51 import kotlinx.coroutines.flow.onStart
52 import kotlinx.coroutines.launch
53 
54 @SysUISingleton
55 class FromLockscreenTransitionInteractor
56 @Inject
57 constructor(
58     override val transitionRepository: KeyguardTransitionRepository,
59     transitionInteractor: KeyguardTransitionInteractor,
60     @Background private val scope: CoroutineScope,
61     @Background bgDispatcher: CoroutineDispatcher,
62     @Main mainDispatcher: CoroutineDispatcher,
63     keyguardInteractor: KeyguardInteractor,
64     private val shadeRepository: ShadeRepository,
65     powerInteractor: PowerInteractor,
66     private val glanceableHubTransitions: GlanceableHubTransitions,
67     private val swipeToDismissInteractor: SwipeToDismissInteractor,
68     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
69 ) :
70     TransitionInteractor(
71         fromState = KeyguardState.LOCKSCREEN,
72         transitionInteractor = transitionInteractor,
73         mainDispatcher = mainDispatcher,
74         bgDispatcher = bgDispatcher,
75         powerInteractor = powerInteractor,
76         keyguardOcclusionInteractor = keyguardOcclusionInteractor,
77         keyguardInteractor = keyguardInteractor,
78     ) {
79 
80     override fun start() {
81         listenForLockscreenToGone()
82         listenForLockscreenToGoneDragging()
83         listenForLockscreenToOccludedOrDreaming()
84         listenForLockscreenToAodOrDozing()
85         listenForLockscreenToPrimaryBouncer()
86         listenForLockscreenToDreaming()
87         listenForLockscreenToPrimaryBouncerDragging()
88         listenForLockscreenToAlternateBouncer()
89         listenForLockscreenTransitionToCamera()
90         listenForLockscreenToGlanceableHub()
91     }
92 
93     /**
94      * Whether we want the surface behind the keyguard visible for the transition from LOCKSCREEN,
95      * or null if we don't care and should just use a reasonable default.
96      *
97      * [KeyguardSurfaceBehindInteractor] will switch to this flow whenever a transition from
98      * LOCKSCREEN is running.
99      */
100     val surfaceBehindVisibility: Flow<Boolean?> =
101         transitionInteractor
102             .transition(
103                 edge = Edge.create(from = KeyguardState.LOCKSCREEN, to = Scenes.Gone),
104                 edgeWithoutSceneContainer =
105                     Edge.create(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
106             )
107             .map<TransitionStep, Boolean?> {
108                 true // Make the surface visible during LS -> GONE transitions.
109             }
110             .onStart {
111                 // Default to null ("don't care, use a reasonable default").
112                 emit(null)
113             }
114             .distinctUntilChanged()
115 
116     private fun listenForLockscreenTransitionToCamera() {
117         listenForTransitionToCamera(scope, keyguardInteractor)
118     }
119 
120     private fun listenForLockscreenToDreaming() {
121         if (KeyguardWmStateRefactor.isEnabled) {
122             return
123         }
124 
125         val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
126         scope.launch("$TAG#listenForLockscreenToDreaming") {
127             keyguardInteractor.isAbleToDream
128                 .filterRelevantKeyguardState()
129                 .sampleCombine(
130                     transitionInteractor.currentTransitionInfoInternal,
131                     finishedKeyguardState,
132                     keyguardInteractor.isActiveDreamLockscreenHosted,
133                 )
134                 .collect {
135                     (
136                         isAbleToDream,
137                         transitionInfo,
138                         finishedKeyguardState,
139                         isActiveDreamLockscreenHosted) ->
140                     val isOnLockscreen = finishedKeyguardState == KeyguardState.LOCKSCREEN
141                     val isTransitionInterruptible =
142                         transitionInfo.to == KeyguardState.LOCKSCREEN &&
143                             !invalidFromStates.contains(transitionInfo.from)
144                     if (isAbleToDream && (isOnLockscreen || isTransitionInterruptible)) {
145                         if (isActiveDreamLockscreenHosted) {
146                             startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
147                         } else {
148                             startTransitionTo(KeyguardState.DREAMING)
149                         }
150                     }
151                 }
152         }
153     }
154 
155     private fun listenForLockscreenToPrimaryBouncer() {
156         if (SceneContainerFlag.isEnabled) return
157         scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") {
158             keyguardInteractor.primaryBouncerShowing
159                 .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing }
160                 .collect {
161                     startTransitionTo(
162                         KeyguardState.PRIMARY_BOUNCER,
163                         ownerReason = "#listenForLockscreenToPrimaryBouncer"
164                     )
165                 }
166         }
167     }
168 
169     private fun listenForLockscreenToAlternateBouncer() {
170         scope.launch("$TAG#listenForLockscreenToAlternateBouncer") {
171             keyguardInteractor.alternateBouncerShowing
172                 .filterRelevantKeyguardStateAnd { isAlternateBouncerShowing ->
173                     isAlternateBouncerShowing
174                 }
175                 .collect { pair -> startTransitionTo(KeyguardState.ALTERNATE_BOUNCER) }
176         }
177     }
178 
179     /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
180     private fun listenForLockscreenToPrimaryBouncerDragging() {
181         if (SceneContainerFlag.isEnabled) return
182         var transitionId: UUID? = null
183         scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
184             shadeRepository.legacyShadeExpansion
185                 .sampleCombine(
186                     startedKeyguardTransitionStep,
187                     transitionInteractor.currentTransitionInfoInternal,
188                     keyguardInteractor.statusBarState,
189                     keyguardInteractor.isKeyguardDismissible,
190                 )
191                 .collect {
192                     (
193                         shadeExpansion,
194                         startedStep,
195                         currentTransitionInfo,
196                         statusBarState,
197                         isKeyguardUnlocked) ->
198                     val id = transitionId
199                     if (id != null) {
200                         if (startedStep.to == KeyguardState.PRIMARY_BOUNCER) {
201                             // An existing `id` means a transition is started, and calls to
202                             // `updateTransition` will control it until FINISHED or CANCELED
203                             var nextState =
204                                 if (shadeExpansion == 0f) {
205                                     TransitionState.FINISHED
206                                 } else if (shadeExpansion == 1f) {
207                                     TransitionState.CANCELED
208                                 } else {
209                                     TransitionState.RUNNING
210                                 }
211                             transitionRepository.updateTransition(
212                                 id,
213                                 // This maps the shadeExpansion to a much faster curve, to match
214                                 // the existing logic
215                                 1f - MathUtils.constrainedMap(0f, 1f, 0.95f, 1f, shadeExpansion),
216                                 nextState,
217                             )
218 
219                             if (
220                                 nextState == TransitionState.CANCELED ||
221                                     nextState == TransitionState.FINISHED
222                             ) {
223                                 transitionId = null
224                             }
225 
226                             // If canceled, just put the state back
227                             // TODO(b/278086361): This logic should happen in
228                             //  FromPrimaryBouncerInteractor.
229                             if (nextState == TransitionState.CANCELED) {
230                                 transitionRepository.startTransition(
231                                     TransitionInfo(
232                                         ownerName = name,
233                                         from = KeyguardState.PRIMARY_BOUNCER,
234                                         to = KeyguardState.LOCKSCREEN,
235                                         animator =
236                                             getDefaultAnimatorForTransitionsToState(
237                                                     KeyguardState.LOCKSCREEN
238                                                 )
239                                                 .apply { duration = 0 }
240                                     )
241                                 )
242                             }
243                         }
244                     } else {
245                         // TODO (b/251849525): Remove statusbarstate check when that state is
246                         // integrated into KeyguardTransitionRepository
247                         if (
248                             // Use currentTransitionInfo to decide whether to start the transition.
249                             currentTransitionInfo.to == KeyguardState.LOCKSCREEN &&
250                                 shadeRepository.legacyShadeTracking.value &&
251                                 !isKeyguardUnlocked &&
252                                 statusBarState == KEYGUARD
253                         ) {
254                             transitionId =
255                                 startTransitionTo(
256                                     toState = KeyguardState.PRIMARY_BOUNCER,
257                                     animator = null, // transition will be manually controlled,
258                                     ownerReason = "#listenForLockscreenToPrimaryBouncerDragging"
259                                 )
260                         }
261                     }
262                 }
263         }
264     }
265 
266     fun dismissKeyguard() {
267         scope.launch("$TAG#dismissKeyguard") {
268             startTransitionTo(KeyguardState.GONE, ownerReason = "#dismissKeyguard()")
269         }
270     }
271 
272     private fun listenForLockscreenToGone() {
273         if (KeyguardWmStateRefactor.isEnabled) {
274             return
275         }
276 
277         scope.launch("$TAG#listenForLockscreenToGone") {
278             keyguardInteractor.isKeyguardGoingAway
279                 .filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
280                 .collect {
281                     startTransitionTo(
282                         KeyguardState.GONE,
283                         modeOnCanceled = TransitionModeOnCanceled.RESET,
284                     )
285                 }
286         }
287     }
288 
289     private fun listenForLockscreenToGoneDragging() {
290         if (SceneContainerFlag.isEnabled) return
291         if (KeyguardWmStateRefactor.isEnabled) {
292             // When the refactor is enabled, we no longer use isKeyguardGoingAway.
293             scope.launch("$TAG#listenForLockscreenToGoneDragging") {
294                 swipeToDismissInteractor.dismissFling
295                     .filterNotNull()
296                     .filterRelevantKeyguardState()
297                     .collect { _ -> startTransitionTo(KeyguardState.GONE) }
298             }
299         }
300     }
301 
302     private fun listenForLockscreenToOccludedOrDreaming() {
303         if (KeyguardWmStateRefactor.isEnabled) {
304             scope.launch("$TAG#listenForLockscreenToOccludedOrDreaming") {
305                 keyguardOcclusionInteractor.showWhenLockedActivityInfo
306                     .filterRelevantKeyguardStateAnd { it.isOnTop }
307                     .collect { taskInfo ->
308                         startTransitionTo(
309                             if (taskInfo.isDream()) {
310                                 KeyguardState.DREAMING
311                             } else {
312                                 KeyguardState.OCCLUDED
313                             }
314                         )
315                     }
316             }
317         } else {
318             scope.launch("$TAG#listenForLockscreenToOccludedOrDreaming") {
319                 keyguardInteractor.isKeyguardOccluded
320                     .filterRelevantKeyguardStateAnd { isOccluded -> isOccluded }
321                     .collect { startTransitionTo(KeyguardState.OCCLUDED) }
322             }
323         }
324     }
325 
326     private fun listenForLockscreenToAodOrDozing() {
327         scope.launch("$TAG#listenForLockscreenToAodOrDozing") {
328             listenForSleepTransition(
329                 modeOnCanceledFromStartedStep = { startedStep ->
330                     if (
331                         transitionInteractor.asleepKeyguardState.value == KeyguardState.AOD &&
332                             startedStep.from == KeyguardState.AOD
333                     ) {
334                         TransitionModeOnCanceled.REVERSE
335                     } else {
336                         TransitionModeOnCanceled.LAST_VALUE
337                     }
338                 }
339             )
340         }
341     }
342 
343     /**
344      * Listens for transition from glanceable hub back to lock screen and directly drives the
345      * keyguard transition.
346      */
347     private fun listenForLockscreenToGlanceableHub() {
348         // TODO(b/336576536): Check if adaptation for scene framework is needed
349         if (SceneContainerFlag.isEnabled) return
350         if (!Flags.communalHub()) {
351             return
352         }
353         scope.launch(mainDispatcher) {
354             glanceableHubTransitions.listenForGlanceableHubTransition(
355                 transitionOwnerName = TAG,
356                 fromState = KeyguardState.LOCKSCREEN,
357                 toState = KeyguardState.GLANCEABLE_HUB,
358             )
359         }
360     }
361 
362     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
363         return ValueAnimator().apply {
364             interpolator = Interpolators.LINEAR
365             duration =
366                 when (toState) {
367                     // Adds 100ms to the overall delay to workaround legacy setOccluded calls
368                     // being delayed in KeyguardViewMediator
369                     KeyguardState.DREAMING -> TO_DREAMING_DURATION + 100.milliseconds
370                     KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION
371                     KeyguardState.AOD -> TO_AOD_DURATION
372                     KeyguardState.DOZING -> TO_DOZING_DURATION
373                     KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> TO_DREAMING_HOSTED_DURATION
374                     KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
375                     else -> DEFAULT_DURATION
376                 }.inWholeMilliseconds
377         }
378     }
379 
380     companion object {
381         private const val TAG = "FromLockscreenTransitionInteractor"
382         private val DEFAULT_DURATION = 400.milliseconds
383         val TO_DOZING_DURATION = 500.milliseconds
384         val TO_DREAMING_DURATION = 933.milliseconds
385         val TO_DREAMING_HOSTED_DURATION = 933.milliseconds
386         val TO_OCCLUDED_DURATION = 450.milliseconds
387         val TO_AOD_DURATION = 500.milliseconds
388         val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION
389         val TO_GONE_DURATION = 633.milliseconds
390         val TO_GLANCEABLE_HUB_DURATION = 1.seconds
391     }
392 }
393