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 @file:OptIn(ExperimentalCoroutinesApi::class)
18 
19 package com.android.systemui.keyguard.domain.interactor
20 
21 import com.android.compose.animation.scene.ObservableTransitionState
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
24 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
25 import com.android.systemui.keyguard.shared.model.Edge
26 import com.android.systemui.keyguard.shared.model.KeyguardState
27 import com.android.systemui.keyguard.shared.model.TransitionState
28 import com.android.systemui.scene.domain.interactor.SceneInteractor
29 import com.android.systemui.scene.shared.flag.SceneContainerFlag
30 import com.android.systemui.scene.shared.model.Scenes
31 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
32 import com.android.systemui.util.kotlin.sample
33 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
34 import dagger.Lazy
35 import javax.inject.Inject
36 import kotlinx.coroutines.ExperimentalCoroutinesApi
37 import kotlinx.coroutines.flow.Flow
38 import kotlinx.coroutines.flow.combine
39 import kotlinx.coroutines.flow.distinctUntilChanged
40 import kotlinx.coroutines.flow.flatMapLatest
41 import kotlinx.coroutines.flow.flowOf
42 import kotlinx.coroutines.flow.map
43 
44 @SysUISingleton
45 class WindowManagerLockscreenVisibilityInteractor
46 @Inject
47 constructor(
48     keyguardInteractor: KeyguardInteractor,
49     transitionInteractor: KeyguardTransitionInteractor,
50     surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
51     fromLockscreenInteractor: FromLockscreenTransitionInteractor,
52     fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
53     fromAlternateBouncerInteractor: FromAlternateBouncerTransitionInteractor,
54     notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
55     sceneInteractor: Lazy<SceneInteractor>,
56     deviceEntryInteractor: Lazy<DeviceEntryInteractor>,
57 ) {
58     private val defaultSurfaceBehindVisibility =
59         transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
60 
61     /**
62      * Surface visibility provided by the From*TransitionInteractor responsible for the currently
63      * RUNNING transition, or null if the current transition does not require special surface
64      * visibility handling.
65      *
66      * An example of transition-specific visibility is swipe to unlock, where the surface should
67      * only be visible after swiping 20% of the way up the screen, and should become invisible again
68      * if the user swipes back down.
69      */
70     @OptIn(ExperimentalCoroutinesApi::class)
71     private val transitionSpecificSurfaceBehindVisibility: Flow<Boolean?> =
72         transitionInteractor.startedKeyguardTransitionStep
73             .flatMapLatest { startedStep ->
74                 when (startedStep.from) {
75                     KeyguardState.LOCKSCREEN -> {
76                         fromLockscreenInteractor.surfaceBehindVisibility
77                     }
78                     KeyguardState.PRIMARY_BOUNCER -> {
79                         fromBouncerInteractor.surfaceBehindVisibility
80                     }
81                     KeyguardState.ALTERNATE_BOUNCER -> {
82                         fromAlternateBouncerInteractor.surfaceBehindVisibility
83                     }
84                     else -> flowOf(null)
85                 }
86             }
87             .distinctUntilChanged()
88 
89     private val isDeviceEntered: Flow<Boolean> by lazy {
90         deviceEntryInteractor.get().isDeviceEntered
91     }
92 
93     private val isDeviceNotEntered: Flow<Boolean> by lazy { isDeviceEntered.map { !it } }
94 
95     /**
96      * Surface visibility, which is either determined by the default visibility when not
97      * transitioning between [KeyguardState]s or [Scenes] or the transition-specific visibility used
98      * during certain ongoing transitions.
99      */
100     @OptIn(ExperimentalCoroutinesApi::class)
101     val surfaceBehindVisibility: Flow<Boolean> =
102         if (SceneContainerFlag.isEnabled) {
103                 sceneInteractor.get().transitionState.flatMapLatestConflated { transitionState ->
104                     when (transitionState) {
105                         is ObservableTransitionState.Transition ->
106                             when {
107                                 transitionState.fromScene == Scenes.Lockscreen &&
108                                     transitionState.toScene == Scenes.Gone ->
109                                     sceneInteractor
110                                         .get()
111                                         .isTransitionUserInputOngoing
112                                         .flatMapLatestConflated { isUserInputOngoing ->
113                                             if (isUserInputOngoing) {
114                                                 isDeviceEntered
115                                             } else {
116                                                 flowOf(true)
117                                             }
118                                         }
119                                 transitionState.fromScene == Scenes.Bouncer &&
120                                     transitionState.toScene == Scenes.Gone ->
121                                     transitionState.progress.map { progress ->
122                                         progress >
123                                             FromPrimaryBouncerTransitionInteractor
124                                                 .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD
125                                     }
126                                 else -> isDeviceEntered
127                             }
128                         is ObservableTransitionState.Idle -> isDeviceEntered
129                     }
130                 }
131             } else {
132                 transitionInteractor.isInTransitionToAnyState.flatMapLatest { isInTransition ->
133                     if (!isInTransition) {
134                         defaultSurfaceBehindVisibility
135                     } else {
136                         combine(
137                             transitionSpecificSurfaceBehindVisibility,
138                             defaultSurfaceBehindVisibility,
139                         ) { transitionVisibility, defaultVisibility ->
140                             // Defer to the transition-specific visibility since we're RUNNING a
141                             // transition, but fall back to the default visibility if the current
142                             // transition's interactor did not specify a visibility.
143                             transitionVisibility ?: defaultVisibility
144                         }
145                     }
146                 }
147             }
148             .distinctUntilChanged()
149 
150     /**
151      * Whether we're animating, or intend to animate, the surface behind the keyguard via remote
152      * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it.
153      */
154     val usingKeyguardGoingAwayAnimation: Flow<Boolean> =
155         if (SceneContainerFlag.isEnabled) {
156             combine(
157                     sceneInteractor.get().transitionState,
158                     surfaceBehindInteractor.isAnimatingSurface,
159                     notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
160                 ) { transition, isAnimatingSurface, isLaunchAnimationRunning ->
161                     // Using the animation if we're animating it directly, or if the
162                     // ActivityLaunchAnimator is in the process of animating it.
163                     val isAnyAnimationRunning = isAnimatingSurface || isLaunchAnimationRunning
164                     // We may still be animating the surface after the keyguard is fully GONE, since
165                     // some animations (like the translation spring) are not tied directly to the
166                     // transition step amount.
167                     transition.isTransitioning(to = Scenes.Gone) ||
168                         (isAnyAnimationRunning &&
169                             (transition.isIdle(Scenes.Gone) ||
170                                 transition.isTransitioning(from = Scenes.Gone)))
171                 }
172                 .distinctUntilChanged()
173         } else {
174             combine(
175                     transitionInteractor.isInTransition(
176                         edge = Edge.create(to = Scenes.Gone),
177                         edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GONE)
178                     ),
179                     transitionInteractor.finishedKeyguardState,
180                     surfaceBehindInteractor.isAnimatingSurface,
181                     notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
182                 ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning ->
183                     // Using the animation if we're animating it directly, or if the
184                     // ActivityLaunchAnimator is in the process of animating it.
185                     val animationsRunning = isAnimatingSurface || notifLaunchRunning
186                     // We may still be animating the surface after the keyguard is fully GONE, since
187                     // some animations (like the translation spring) are not tied directly to the
188                     // transition step amount.
189                     isInTransitionToGone ||
190                         (finishedState == KeyguardState.GONE && animationsRunning)
191                 }
192                 .distinctUntilChanged()
193         }
194 
195     /**
196      * Whether the lockscreen is visible, from the Window Manager (WM) perspective.
197      *
198      * Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
199      * only inform WM once we're done with the keyguard and we're fully GONE. Don't use this if you
200      * want to know if the AOD/clock/notifs/etc. are visible.
201      */
202     val lockscreenVisibility: Flow<Boolean> =
203         if (SceneContainerFlag.isEnabled) {
204             isDeviceNotEntered
205         } else {
206             transitionInteractor.currentKeyguardState
207                 .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair)
208                 .map { (currentState, startedWithPrev) ->
209                     val startedFromStep = startedWithPrev?.previousValue
210                     val startedStep = startedWithPrev?.newValue
211                     val returningToGoneAfterCancellation =
212                         startedStep?.to == KeyguardState.GONE &&
213                             startedFromStep?.transitionState == TransitionState.CANCELED &&
214                             startedFromStep.from == KeyguardState.GONE
215 
216                     if (!returningToGoneAfterCancellation) {
217                         // By default, apply the lockscreen visibility of the current state.
218                         deviceEntryInteractor.get().isLockscreenEnabled() &&
219                             KeyguardState.lockscreenVisibleInState(currentState)
220                     } else {
221                         // If we're transitioning to GONE after a prior canceled transition from
222                         // GONE, then this is the camera launch transition from an asleep state back
223                         // to GONE. We don't want to show the lockscreen since we're aborting the
224                         // lock and going back to GONE.
225                         KeyguardState.lockscreenVisibleInState(KeyguardState.GONE)
226                     }
227                 }
228                 .distinctUntilChanged()
229         }
230 
231     /**
232      * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
233      * manager's perspective.
234      *
235      * Note: This may be true even if AOD is not user-visible, such as when the light sensor
236      * indicates the device is in the user's pocket. Don't use this if you want to know if the AOD
237      * clock/smartspace/notif icons are visible.
238      */
239     val aodVisibility: Flow<Boolean> =
240         combine(
241                 keyguardInteractor.isDozing,
242                 keyguardInteractor.isAodAvailable,
243                 keyguardInteractor.biometricUnlockState,
244             ) { isDozing, isAodAvailable, biometricUnlockState ->
245                 // AOD is visible if we're dozing, unless we are wake and unlocking (where we go
246                 // directly from AOD to unlocked while dozing).
247                 isDozing &&
248                     isAodAvailable &&
249                     !BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode)
250             }
251             .distinctUntilChanged()
252 
253     companion object {
254         fun isSurfaceVisible(state: KeyguardState): Boolean {
255             return !KeyguardState.lockscreenVisibleInState(state)
256         }
257     }
258 }
259