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