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