• 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 com.android.compose.animation.scene.ObservableTransitionState.Idle
20 import com.android.compose.animation.scene.ObservableTransitionState.Transition
21 import com.android.systemui.Flags.transitionRaceCondition
22 import com.android.systemui.dagger.SysUISingleton
23 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
24 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
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.KeyguardState.Companion.deviceIsAsleepInState
28 import com.android.systemui.keyguard.shared.model.TransitionState
29 import com.android.systemui.scene.domain.interactor.SceneInteractor
30 import com.android.systemui.scene.shared.flag.SceneContainerFlag
31 import com.android.systemui.scene.shared.model.Overlays
32 import com.android.systemui.scene.shared.model.Scenes
33 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
34 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
35 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
36 import dagger.Lazy
37 import javax.inject.Inject
38 import kotlinx.coroutines.flow.Flow
39 import kotlinx.coroutines.flow.combine
40 import kotlinx.coroutines.flow.distinctUntilChanged
41 import kotlinx.coroutines.flow.flatMapLatest
42 import kotlinx.coroutines.flow.flowOf
43 import kotlinx.coroutines.flow.map
44 
45 @SysUISingleton
46 class WindowManagerLockscreenVisibilityInteractor
47 @Inject
48 constructor(
49     keyguardInteractor: KeyguardInteractor,
50     transitionRepository: KeyguardTransitionRepository,
51     transitionInteractor: KeyguardTransitionInteractor,
52     surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
53     fromLockscreenInteractor: FromLockscreenTransitionInteractor,
54     fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
55     fromAlternateBouncerInteractor: FromAlternateBouncerTransitionInteractor,
56     notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
57     sceneInteractor: Lazy<SceneInteractor>,
58     deviceEntryInteractor: Lazy<DeviceEntryInteractor>,
59     wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
60 ) {
61     private val defaultSurfaceBehindVisibility =
62         combine(
63             transitionInteractor.isFinishedIn(
64                 content = Scenes.Gone,
65                 stateWithoutSceneContainer = KeyguardState.GONE,
66             ),
67             wakeToGoneInteractor.canWakeDirectlyToGone,
68         ) { isOnGone, canWakeDirectlyToGone ->
69             isOnGone || canWakeDirectlyToGone
70         }
71 
72     /**
73      * Surface visibility provided by the From*TransitionInteractor responsible for the currently
74      * RUNNING transition, or null if the current transition does not require special surface
75      * visibility handling.
76      *
77      * An example of transition-specific visibility is swipe to unlock, where the surface should
78      * only be visible after swiping 20% of the way up the screen, and should become invisible again
79      * if the user swipes back down.
80      */
81     private val transitionSpecificSurfaceBehindVisibility: Flow<Boolean?> =
82         transitionInteractor.startedKeyguardTransitionStep
83             .flatMapLatest { startedStep ->
84                 SceneContainerFlag.assertInLegacyMode()
85                 when (startedStep.from) {
86                     KeyguardState.LOCKSCREEN -> {
87                         fromLockscreenInteractor.surfaceBehindVisibility
88                     }
89                     KeyguardState.PRIMARY_BOUNCER -> {
90                         fromBouncerInteractor.surfaceBehindVisibility
91                     }
92                     KeyguardState.ALTERNATE_BOUNCER -> {
93                         fromAlternateBouncerInteractor.surfaceBehindVisibility
94                     }
95                     KeyguardState.OCCLUDED -> {
96                         // OCCLUDED -> GONE occurs when an app is on top of the keyguard, and then
97                         // requests manual dismissal of the keyguard in the background. The app will
98                         // remain visible on top of the stack throughout this transition, so we
99                         // should not trigger the keyguard going away animation by returning
100                         // surfaceBehindVisibility = true.
101                         flowOf(false)
102                     }
103                     else -> flowOf(null)
104                 }
105             }
106             .distinctUntilChanged()
107 
108     private val isDeviceEnteredDirectly by lazy {
109         deviceEntryInteractor.get().isDeviceEnteredDirectly
110     }
111     private val isDeviceNotEnteredDirectly by lazy { isDeviceEnteredDirectly.map { !it } }
112 
113     /**
114      * Surface visibility, which is either determined by the default visibility when not
115      * transitioning between [KeyguardState]s or [Scenes] or the transition-specific visibility used
116      * during certain ongoing transitions.
117      */
118     val surfaceBehindVisibility: Flow<Boolean> =
119         if (SceneContainerFlag.isEnabled) {
120                 sceneInteractor.get().transitionState.flatMapLatestConflated { state ->
121                     when {
122                         state.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Gone) ->
123                             isDeviceEnteredDirectly
124                         state.isTransitioning(from = Overlays.Bouncer, to = Scenes.Gone) ->
125                             (state as Transition).progress.map { progress ->
126                                 progress >
127                                     FromPrimaryBouncerTransitionInteractor
128                                         .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD
129                             }
130                         else -> lockscreenVisibilityWithScenes.map { !it }
131                     }
132                 }
133             } else {
134                 transitionInteractor.isInTransition.flatMapLatest { isInTransition ->
135                     if (!isInTransition) {
136                         defaultSurfaceBehindVisibility
137                     } else {
138                         combine(
139                             transitionSpecificSurfaceBehindVisibility,
140                             defaultSurfaceBehindVisibility,
141                         ) { transitionVisibility, defaultVisibility ->
142                             // Defer to the transition-specific visibility since we're RUNNING a
143                             // transition, but fall back to the default visibility if the current
144                             // transition's interactor did not specify a visibility.
145                             transitionVisibility ?: defaultVisibility
146                         }
147                     }
148                 }
149             }
150             .distinctUntilChanged()
151 
152     /**
153      * Whether we're animating, or intend to animate, the surface behind the keyguard via remote
154      * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it.
155      */
156     val usingKeyguardGoingAwayAnimation: Flow<Boolean> =
157         if (SceneContainerFlag.isEnabled) {
158             combine(
159                     sceneInteractor.get().transitionState,
160                     surfaceBehindInteractor.isAnimatingSurface,
161                     notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
162                 ) { transition, isAnimatingSurface, isLaunchAnimationRunning ->
163                     // Using the animation if we're animating it directly, or if the
164                     // ActivityLaunchAnimator is in the process of animating it.
165                     val isAnyAnimationRunning = isAnimatingSurface || isLaunchAnimationRunning
166                     // We may still be animating the surface after the keyguard is fully GONE, since
167                     // some animations (like the translation spring) are not tied directly to the
168                     // transition step amount.
169                     transition.isTransitioning(to = Scenes.Gone) ||
170                         (isAnyAnimationRunning &&
171                             (transition.isIdle(Scenes.Gone) ||
172                                 transition.isTransitioning(from = Scenes.Gone)))
173                 }
174                 .distinctUntilChanged()
175         } else {
176             combine(
177                     transitionInteractor.isInTransition(
178                         edge = Edge.create(to = Scenes.Gone),
179                         edgeWithoutSceneContainer = Edge.create(to = KeyguardState.GONE),
180                     ),
181                     transitionInteractor.isFinishedIn(
182                         content = Scenes.Gone,
183                         stateWithoutSceneContainer = KeyguardState.GONE,
184                     ),
185                     surfaceBehindInteractor.isAnimatingSurface,
186                     notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
187                 ) { isInTransitionToGone, isOnGone, isAnimatingSurface, notifLaunchRunning ->
188                     // Using the animation if we're animating it directly, or if the
189                     // ActivityLaunchAnimator is in the process of animating it.
190                     val animationsRunning = isAnimatingSurface || notifLaunchRunning
191                     // We may still be animating the surface after the keyguard is fully GONE, since
192                     // some animations (like the translation spring) are not tied directly to the
193                     // transition step amount.
194                     isInTransitionToGone || (isOnGone && animationsRunning)
195                 }
196                 .distinctUntilChanged()
197         }
198 
199     private val lockscreenVisibilityWithScenes: Flow<Boolean> =
200         // The scene container visibility into account as that will be forced to false when the
201         // device isn't yet provisioned (e.g. still in the setup wizard).
202         sceneInteractor.get().isVisible.flatMapLatestConflated { isVisible ->
203             if (isVisible) {
204                 combine(
205                         sceneInteractor.get().transitionState.flatMapLatestConflated {
206                             when (it) {
207                                 is Idle ->
208                                     when (it.currentScene) {
209                                         in keyguardContent -> flowOf(true)
210                                         in nonKeyguardContent -> flowOf(false)
211                                         in keyguardAgnosticContent -> isDeviceNotEnteredDirectly
212                                         else ->
213                                             throw IllegalStateException(
214                                                 "Unknown scene: ${it.currentScene}"
215                                             )
216                                     }
217                                 is Transition ->
218                                     when {
219                                         it.isTransitioningSets(from = keyguardContent) ->
220                                             flowOf(true)
221                                         it.isTransitioningSets(from = nonKeyguardContent) ->
222                                             flowOf(false)
223                                         it.isTransitioningSets(from = keyguardAgnosticContent) ->
224                                             isDeviceNotEnteredDirectly
225                                         else ->
226                                             throw IllegalStateException(
227                                                 "Unknown content: ${it.fromContent}"
228                                             )
229                                     }
230                             }
231                         },
232                         wakeToGoneInteractor.canWakeDirectlyToGone,
233                         ::Pair,
234                     )
235                     .map { (lockscreenVisibilityByTransitionState, canWakeDirectlyToGone) ->
236                         lockscreenVisibilityByTransitionState && !canWakeDirectlyToGone
237                     }
238             } else {
239                 // Lockscreen is never visible when the scene container is invisible.
240                 flowOf(false)
241             }
242         }
243 
244     private val lockscreenVisibilityLegacy =
245         combine(
246                 transitionInteractor.currentKeyguardState,
247                 transitionInteractor.startedStepWithPrecedingStep,
248                 wakeToGoneInteractor.canWakeDirectlyToGone,
249                 surfaceBehindVisibility,
250                 ::toQuad,
251             )
252             .map { (currentState, startedWithPrev, canWakeDirectlyToGone, surfaceBehindVis) ->
253                 val startedFromStep = startedWithPrev.previousValue
254                 val startedStep = startedWithPrev.newValue
255                 val returningToGoneAfterCancellation =
256                     startedStep.to == KeyguardState.GONE &&
257                         startedFromStep.transitionState == TransitionState.CANCELED &&
258                         startedFromStep.from == KeyguardState.GONE
259 
260                 val transitionInfo =
261                     if (transitionRaceCondition()) {
262                         transitionRepository.currentTransitionInfo
263                     } else {
264                         transitionRepository.currentTransitionInfoInternal.value
265                     }
266                 val wakingDirectlyToGone =
267                     deviceIsAsleepInState(transitionInfo.from) &&
268                         transitionInfo.to == KeyguardState.GONE
269 
270                 if (returningToGoneAfterCancellation || wakingDirectlyToGone) {
271                     // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition,
272                     // which means we never want to show the lockscreen throughout the
273                     // transition. Same for waking directly to gone, due to the lockscreen being
274                     // disabled or because the device was woken back up before the lock timeout
275                     // duration elapsed.
276                     false
277                 } else if (canWakeDirectlyToGone) {
278                     // Never show the lockscreen if we can wake directly to GONE. This means
279                     // that the lock timeout has not yet elapsed, or the keyguard is disabled.
280                     // In either case, we don't show the activity lock screen until one of those
281                     // conditions changes.
282                     false
283                 } else if (
284                     currentState == KeyguardState.DREAMING &&
285                         if (SceneContainerFlag.isEnabled) {
286                             deviceEntryInteractor.get().isUnlocked.value
287                         } else {
288                             keyguardInteractor.isKeyguardDismissible.value
289                         }
290                 ) {
291                     // Dreams dismiss keyguard and return to GONE if they can.
292                     false
293                 } else if (
294                     startedWithPrev.newValue.from == KeyguardState.OCCLUDED &&
295                         startedWithPrev.newValue.to == KeyguardState.GONE
296                 ) {
297                     // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs
298                     // when an app uses intent flags to launch over an insecure keyguard without
299                     // dismissing it, and then manually requests keyguard dismissal while
300                     // OCCLUDED. This transition is not user-visible; the device unlocks in the
301                     // background and the app remains on top, while we're now GONE. In this case
302                     // we should simply tell WM that the lockscreen is no longer visible, and
303                     // *not* play the going away animation or related animations.
304                     false
305                 } else if (!surfaceBehindVis) {
306                     // If the surface behind is not visible, then the lockscreen has to be visible
307                     // since there's nothing to show. The surface behind will never be invisible if
308                     // the lockscreen is disabled or suppressed.
309                     true
310                 } else {
311                     currentState != KeyguardState.GONE
312                 }
313             }
314 
315     /**
316      * Whether the lockscreen is visible, from the Window Manager (WM) perspective.
317      *
318      * Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
319      * only inform WM once we're done with the keyguard and we're fully GONE. Don't use this if you
320      * want to know if the AOD/clock/notifs/etc. are visible.
321      */
322     val lockscreenVisibility: Flow<Boolean> =
323         if (SceneContainerFlag.isEnabled) {
324                 lockscreenVisibilityWithScenes
325             } else {
326                 lockscreenVisibilityLegacy
327             }
328             .distinctUntilChanged()
329 
330     /**
331      * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
332      * manager's perspective.
333      *
334      * Note: This may be true even if AOD is not user-visible, such as when the light sensor
335      * indicates the device is in the user's pocket. Don't use this if you want to know if the AOD
336      * clock/smartspace/notif icons are visible.
337      */
338     val aodVisibility: Flow<Boolean> =
339         transitionInteractor
340             .transitionValue(KeyguardState.AOD)
341             .map { it == 1f }
342             .distinctUntilChanged()
343 
344     companion object {
345         /**
346          * Content that is part of the keyguard and are shown when the device is locked or when the
347          * keyguard still needs to be dismissed.
348          */
349         val keyguardContent =
350             setOf(Scenes.Lockscreen, Overlays.Bouncer, Scenes.Communal, Scenes.Dream)
351 
352         /**
353          * Content that doesn't belong in the keyguard family and cannot show when the device is
354          * locked or when the keyguard still needs to be dismissed.
355          */
356         private val nonKeyguardContent = setOf(Scenes.Gone)
357 
358         /**
359          * Content that can show regardless of device lock or keyguard dismissal states. Other
360          * sources of state need to be consulted to know whether the device has been entered or not.
361          */
362         private val keyguardAgnosticContent =
363             setOf(
364                 Scenes.Shade,
365                 Scenes.QuickSettings,
366                 Overlays.NotificationsShade,
367                 Overlays.QuickSettingsShade,
368             )
369     }
370 }
371