1 /* <lambda>null2 * Copyright 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.scene.domain.startable 18 19 import android.app.StatusBarManager 20 import com.android.compose.animation.scene.ObservableTransitionState 21 import com.android.compose.animation.scene.OverlayKey 22 import com.android.compose.animation.scene.SceneKey 23 import com.android.internal.logging.UiEventLogger 24 import com.android.keyguard.AuthInteractionProperties 25 import com.android.systemui.CoreStartable 26 import com.android.systemui.Flags 27 import com.android.systemui.animation.ActivityTransitionAnimator 28 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor 29 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel 30 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor 31 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor 32 import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor 33 import com.android.systemui.bouncer.shared.logging.BouncerUiEvent 34 import com.android.systemui.classifier.FalsingCollector 35 import com.android.systemui.classifier.FalsingCollectorActual 36 import com.android.systemui.dagger.SysUISingleton 37 import com.android.systemui.dagger.qualifiers.Application 38 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor 39 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor 40 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor 41 import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor 42 import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource 43 import com.android.systemui.keyguard.DismissCallbackRegistry 44 import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor 45 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 46 import com.android.systemui.keyguard.domain.interactor.TrustInteractor 47 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.keyguardContent 48 import com.android.systemui.log.table.TableLogBuffer 49 import com.android.systemui.model.SceneContainerPlugin 50 import com.android.systemui.model.SysUiState 51 import com.android.systemui.model.updateFlags 52 import com.android.systemui.plugins.FalsingManager 53 import com.android.systemui.plugins.FalsingManager.FalsingBeliefListener 54 import com.android.systemui.power.domain.interactor.PowerInteractor 55 import com.android.systemui.power.shared.model.WakeSleepReason 56 import com.android.systemui.scene.data.model.asIterable 57 import com.android.systemui.scene.data.model.sceneStackOf 58 import com.android.systemui.scene.domain.SceneFrameworkTableLog 59 import com.android.systemui.scene.domain.interactor.DisabledContentInteractor 60 import com.android.systemui.scene.domain.interactor.SceneBackInteractor 61 import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor 62 import com.android.systemui.scene.domain.interactor.SceneInteractor 63 import com.android.systemui.scene.session.shared.SessionStorage 64 import com.android.systemui.scene.shared.flag.SceneContainerFlag 65 import com.android.systemui.scene.shared.logger.SceneLogger 66 import com.android.systemui.scene.shared.model.Overlays 67 import com.android.systemui.scene.shared.model.SceneFamilies 68 import com.android.systemui.scene.shared.model.Scenes 69 import com.android.systemui.shade.domain.interactor.ShadeInteractor 70 import com.android.systemui.shade.domain.interactor.ShadeModeInteractor 71 import com.android.systemui.statusbar.NotificationShadeWindowController 72 import com.android.systemui.statusbar.SysuiStatusBarStateController 73 import com.android.systemui.statusbar.VibratorHelper 74 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor 75 import com.android.systemui.statusbar.phone.CentralSurfaces 76 import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor 77 import com.android.systemui.util.asIndenting 78 import com.android.systemui.util.kotlin.getOrNull 79 import com.android.systemui.util.kotlin.pairwise 80 import com.android.systemui.util.kotlin.sample 81 import com.android.systemui.util.printSection 82 import com.android.systemui.util.println 83 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow 84 import com.google.android.msdl.data.model.MSDLToken 85 import com.google.android.msdl.domain.MSDLPlayer 86 import dagger.Lazy 87 import java.io.PrintWriter 88 import java.util.Optional 89 import javax.inject.Inject 90 import kotlinx.coroutines.CoroutineScope 91 import kotlinx.coroutines.channels.awaitClose 92 import kotlinx.coroutines.coroutineScope 93 import kotlinx.coroutines.flow.Flow 94 import kotlinx.coroutines.flow.SharingStarted 95 import kotlinx.coroutines.flow.collectLatest 96 import kotlinx.coroutines.flow.combine 97 import kotlinx.coroutines.flow.distinctUntilChanged 98 import kotlinx.coroutines.flow.distinctUntilChangedBy 99 import kotlinx.coroutines.flow.filter 100 import kotlinx.coroutines.flow.filterIsInstance 101 import kotlinx.coroutines.flow.filterNotNull 102 import kotlinx.coroutines.flow.flatMapLatest 103 import kotlinx.coroutines.flow.flowOf 104 import kotlinx.coroutines.flow.map 105 import kotlinx.coroutines.flow.mapNotNull 106 import kotlinx.coroutines.flow.stateIn 107 import kotlinx.coroutines.launch 108 109 /** 110 * Hooks up business logic that manipulates the state of the [SceneInteractor] for the system UI 111 * scene container based on state from other systems. 112 */ 113 @SysUISingleton 114 class SceneContainerStartable 115 @Inject 116 constructor( 117 @Application private val applicationScope: CoroutineScope, 118 private val sceneInteractor: SceneInteractor, 119 private val deviceEntryInteractor: DeviceEntryInteractor, 120 private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, 121 private val deviceUnlockedInteractor: DeviceUnlockedInteractor, 122 private val bouncerInteractor: BouncerInteractor, 123 private val keyguardInteractor: KeyguardInteractor, 124 private val sysUiState: SysUiState, 125 private val sceneLogger: SceneLogger, 126 @FalsingCollectorActual private val falsingCollector: FalsingCollector, 127 private val falsingManager: FalsingManager, 128 private val powerInteractor: PowerInteractor, 129 private val simBouncerInteractor: Lazy<SimBouncerInteractor>, 130 private val authenticationInteractor: Lazy<AuthenticationInteractor>, 131 private val windowController: NotificationShadeWindowController, 132 private val deviceProvisioningInteractor: DeviceProvisioningInteractor, 133 private val centralSurfacesOptLazy: Lazy<Optional<CentralSurfaces>>, 134 private val headsUpInteractor: HeadsUpNotificationInteractor, 135 private val occlusionInteractor: SceneContainerOcclusionInteractor, 136 private val faceUnlockInteractor: DeviceEntryFaceAuthInteractor, 137 private val shadeInteractor: ShadeInteractor, 138 private val uiEventLogger: UiEventLogger, 139 private val sceneBackInteractor: SceneBackInteractor, 140 private val shadeSessionStorage: SessionStorage, 141 private val keyguardEnabledInteractor: KeyguardEnabledInteractor, 142 private val dismissCallbackRegistry: DismissCallbackRegistry, 143 private val statusBarStateController: SysuiStatusBarStateController, 144 private val alternateBouncerInteractor: AlternateBouncerInteractor, 145 private val vibratorHelper: VibratorHelper, 146 private val msdlPlayer: MSDLPlayer, 147 private val disabledContentInteractor: DisabledContentInteractor, 148 private val activityTransitionAnimator: ActivityTransitionAnimator, 149 private val shadeModeInteractor: ShadeModeInteractor, 150 @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, 151 private val trustInteractor: TrustInteractor, 152 ) : CoreStartable { 153 private val centralSurfaces: CentralSurfaces? 154 get() = centralSurfacesOptLazy.get().getOrNull() 155 156 private val authInteractionProperties = AuthInteractionProperties() 157 158 override fun start() { 159 if (SceneContainerFlag.isEnabled) { 160 sceneLogger.logFrameworkEnabled(isEnabled = true) 161 applicationScope.launch { hydrateTableLogBuffer() } 162 hydrateVisibility() 163 automaticallySwitchScenes() 164 hydrateSystemUiState() 165 collectFalsingSignals() 166 respondToFalsingDetections() 167 hydrateInteractionState() 168 handleBouncerOverscroll() 169 handleDeviceEntryHapticsWhileDeviceLocked() 170 hydrateWindowController() 171 hydrateBackStack() 172 resetShadeSessions() 173 handleKeyguardEnabledness() 174 notifyKeyguardDismissCancelledCallbacks() 175 refreshLockscreenEnabled() 176 hydrateActivityTransitionAnimationState() 177 lockWhenDeviceBecomesUntrusted() 178 } else { 179 sceneLogger.logFrameworkEnabled( 180 isEnabled = false, 181 reason = SceneContainerFlag.requirementDescription(), 182 ) 183 } 184 } 185 186 override fun dump(pw: PrintWriter, args: Array<out String>) { 187 with(pw.asIndenting()) { 188 printSection("SceneContainerFlag") { 189 printSection("Framework availability") { 190 println("isEnabled", SceneContainerFlag.isEnabled) 191 println(SceneContainerFlag.requirementDescription()) 192 } 193 194 if (!SceneContainerFlag.isEnabled) { 195 return 196 } 197 198 printSection("Framework state") { 199 println("isVisible", sceneInteractor.isVisible.value) 200 println("currentScene", sceneInteractor.currentScene.value.debugName) 201 println( 202 "currentOverlays", 203 sceneInteractor.currentOverlays.value.joinToString(", ") { overlay -> 204 overlay.debugName 205 }, 206 ) 207 println("backStack", sceneBackInteractor.backStack.value) 208 println("shadeMode", shadeModeInteractor.shadeMode.value) 209 } 210 211 printSection("Authentication state") { 212 println("isKeyguardEnabled", keyguardEnabledInteractor.isKeyguardEnabled.value) 213 println( 214 "isUnlocked", 215 deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked, 216 ) 217 println("isDeviceEntered", deviceEntryInteractor.isDeviceEntered.value) 218 println( 219 "isFaceAuthEnabledAndEnrolled", 220 faceUnlockInteractor.isFaceAuthEnabledAndEnrolled(), 221 ) 222 println("canSwipeToEnter", deviceEntryInteractor.canSwipeToEnter.value) 223 } 224 225 printSection("Power state") { 226 println("detailedWakefulness", powerInteractor.detailedWakefulness.value) 227 println("isDozing", keyguardInteractor.isDozing.value) 228 println("isAodAvailable", keyguardInteractor.isAodAvailable.value) 229 } 230 } 231 } 232 } 233 234 private suspend fun hydrateTableLogBuffer() { 235 coroutineScope { 236 launch { sceneInteractor.hydrateTableLogBuffer(tableLogBuffer) } 237 launch { keyguardEnabledInteractor.hydrateTableLogBuffer(tableLogBuffer) } 238 launch { faceUnlockInteractor.hydrateTableLogBuffer(tableLogBuffer) } 239 launch { powerInteractor.hydrateTableLogBuffer(tableLogBuffer) } 240 launch { keyguardInteractor.hydrateTableLogBuffer(tableLogBuffer) } 241 } 242 } 243 244 private fun resetShadeSessions() { 245 applicationScope.launch { 246 combine( 247 sceneBackInteractor.backStack 248 // We are in a session if either Shade or QuickSettings is on the back stack 249 .map { backStack -> 250 backStack.asIterable().any { 251 // TODO(b/356596436): Include overlays in the back stack as well. 252 it == Scenes.Shade || it == Scenes.QuickSettings 253 } 254 } 255 .distinctUntilChanged(), 256 // We are also in a session if either Notifications Shade or QuickSettings Shade 257 // is currently shown (whether idle or animating). 258 shadeInteractor.isAnyExpanded, 259 ) { inBackStack, isShadeShown -> 260 inBackStack || isShadeShown 261 } 262 // Once a session has ended, clear the session storage. 263 .filter { inSession -> !inSession } 264 .collect { shadeSessionStorage.clear() } 265 } 266 } 267 268 /** Updates the visibility of the scene container. */ 269 private fun hydrateVisibility() { 270 applicationScope.launch { 271 deviceProvisioningInteractor.isDeviceProvisioned 272 .flatMapLatest { isAllowedToBeVisible -> 273 if (isAllowedToBeVisible) { 274 combine( 275 sceneInteractor.transitionState.mapNotNull { state -> 276 when (state) { 277 is ObservableTransitionState.Idle -> { 278 if (state.currentScene == Scenes.Dream) { 279 false to "dream is showing" 280 } else if (state.currentScene != Scenes.Gone) { 281 true to "scene is not Gone" 282 } else if (state.currentOverlays.isNotEmpty()) { 283 true to "overlay is shown" 284 } else { 285 false to "scene is Gone and no overlays are shown" 286 } 287 } 288 is ObservableTransitionState.Transition -> { 289 if (state.fromContent == Scenes.Gone) { 290 true to "scene transitioning away from Gone" 291 } else if (state.fromContent == Scenes.Dream) { 292 true to "scene transitioning away from dream" 293 } else { 294 null 295 } 296 } 297 } 298 }, 299 sceneInteractor.transitionState.map { state -> 300 state.isTransitioningFromOrTo(Scenes.Communal) || 301 state.isIdle(Scenes.Communal) 302 }, 303 headsUpInteractor.isHeadsUpOrAnimatingAway, 304 occlusionInteractor.invisibleDueToOcclusion, 305 alternateBouncerInteractor.isVisible, 306 ) { 307 visibilityForTransitionState, 308 isCommunalShowing, 309 isHeadsUpOrAnimatingAway, 310 invisibleDueToOcclusion, 311 isAlternateBouncerVisible -> 312 when { 313 isCommunalShowing -> 314 true to "on or transitioning to/from communal" 315 isHeadsUpOrAnimatingAway -> true to "showing a HUN" 316 isAlternateBouncerVisible -> true to "showing alternate bouncer" 317 invisibleDueToOcclusion -> false to "invisible due to occlusion" 318 else -> visibilityForTransitionState 319 } 320 } 321 .distinctUntilChanged() 322 } else { 323 flowOf(false to "Device not provisioned or Factory Reset Protection active") 324 } 325 } 326 .collect { (isVisible, loggingReason) -> 327 sceneInteractor.setVisible(isVisible, loggingReason) 328 } 329 } 330 } 331 332 /** Switches between scenes based on ever-changing application state. */ 333 private fun automaticallySwitchScenes() { 334 handleBouncerImeVisibility() 335 handleBouncerHiding() 336 handleSimUnlock() 337 handleDeviceUnlockStatus() 338 handlePowerState() 339 handleDreamState() 340 handleShadeTouchability() 341 handleDisableFlags() 342 } 343 344 private fun handleBouncerImeVisibility() { 345 applicationScope.launch { 346 // TODO (b/308001302): Move this to a bouncer specific interactor. 347 bouncerInteractor.onImeHiddenByUser.collectLatest { 348 sceneInteractor.hideOverlay( 349 overlay = Overlays.Bouncer, 350 loggingReason = "IME hidden.", 351 ) 352 } 353 } 354 } 355 356 private fun handleBouncerHiding() { 357 applicationScope.launch { 358 repeatWhen( 359 condition = 360 authenticationInteractor 361 .get() 362 .authenticationMethod 363 .map { !it.isSecure } 364 .distinctUntilChanged() 365 ) { 366 sceneInteractor.hideOverlay( 367 overlay = Overlays.Bouncer, 368 loggingReason = "Authentication method changed to a non-secure one.", 369 ) 370 } 371 } 372 } 373 374 private fun handleSimUnlock() { 375 applicationScope.launch { 376 simBouncerInteractor 377 .get() 378 .isAnySimSecure 379 .sample(deviceUnlockedInteractor.deviceUnlockStatus, ::Pair) 380 .collect { (isAnySimLocked, unlockStatus) -> 381 when { 382 isAnySimLocked -> { 383 sceneInteractor.showOverlay( 384 overlay = Overlays.Bouncer, 385 loggingReason = "Need to authenticate locked SIM card.", 386 ) 387 } 388 unlockStatus.isUnlocked && 389 deviceEntryInteractor.canSwipeToEnter.value == false -> { 390 val loggingReason = 391 "All SIM cards unlocked and device already unlocked and " + 392 "lockscreen doesn't require a swipe to dismiss." 393 switchToScene( 394 targetSceneKey = Scenes.Gone, 395 loggingReason = loggingReason, 396 ) 397 } 398 else -> { 399 val loggingReason = 400 "All SIM cards unlocked and device still locked" + 401 " or lockscreen still requires a swipe to dismiss." 402 switchToScene( 403 targetSceneKey = Scenes.Lockscreen, 404 loggingReason = loggingReason, 405 ) 406 } 407 } 408 } 409 } 410 } 411 412 private fun handleDeviceUnlockStatus() { 413 applicationScope.launch { 414 // Track the previous scene, so that we know where to go when the device is unlocked 415 // whilst on the bouncer. 416 val previousScene = 417 sceneBackInteractor.backScene.stateIn( 418 this, 419 SharingStarted.Eagerly, 420 initialValue = null, 421 ) 422 deviceUnlockedInteractor.deviceUnlockStatus 423 .mapNotNull { deviceUnlockStatus -> 424 val (renderedScenes: List<SceneKey>, renderedOverlays) = 425 when (val transitionState = sceneInteractor.transitionState.value) { 426 is ObservableTransitionState.Idle -> 427 listOf(transitionState.currentScene) to 428 transitionState.currentOverlays 429 is ObservableTransitionState.Transition.ChangeScene -> 430 listOf(transitionState.fromScene, transitionState.toScene) to 431 transitionState.currentOverlays 432 is ObservableTransitionState.Transition.OverlayTransition -> 433 listOf(transitionState.currentScene) to 434 setOfNotNull( 435 transitionState.toContent.takeIf { it is OverlayKey }, 436 transitionState.fromContent.takeIf { it is OverlayKey }, 437 ) 438 } 439 val isOnLockscreen = renderedScenes.contains(Scenes.Lockscreen) 440 val isAlternateBouncerVisible = alternateBouncerInteractor.isVisibleState() 441 val isOnPrimaryBouncer = Overlays.Bouncer in renderedOverlays 442 if (!deviceUnlockStatus.isUnlocked) { 443 return@mapNotNull if ( 444 renderedScenes.any { it in keyguardContent } || 445 Overlays.Bouncer in renderedOverlays 446 ) { 447 // Already on a keyguard scene or bouncer, no need to change scenes. 448 null 449 } else { 450 // The device locked while on a scene that's not a keyguard scene, go 451 // to Lockscreen. 452 Scenes.Lockscreen to "device locked in a non-keyguard scene" 453 } 454 } 455 456 if (powerInteractor.detailedWakefulness.value.isAsleep()) { 457 // The logic below is for when the device becomes unlocked. That must be a 458 // no-op if the device is not awake. 459 return@mapNotNull null 460 } 461 462 if ( 463 isOnPrimaryBouncer && 464 deviceUnlockStatus.deviceUnlockSource == DeviceUnlockSource.TrustAgent 465 ) { 466 uiEventLogger.log(BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS) 467 } 468 when { 469 isAlternateBouncerVisible -> { 470 // When the device becomes unlocked when the alternate bouncer is 471 // showing, always hide the alternate bouncer and notify dismiss 472 // succeeded 473 alternateBouncerInteractor.hide() 474 dismissCallbackRegistry.notifyDismissSucceeded() 475 476 // ... and go to Gone or stay on the current scene 477 if ( 478 isOnLockscreen || 479 !statusBarStateController.leaveOpenOnKeyguardHide() 480 ) { 481 Scenes.Gone to 482 "device was unlocked with alternate bouncer showing" + 483 " and shade didn't need to be left open" 484 } else { 485 replaceLockscreenSceneOnBackStack() 486 null 487 } 488 } 489 isOnPrimaryBouncer -> { 490 // When the device becomes unlocked in primary Bouncer, 491 // notify dismiss succeeded and remain in current scene or switch to 492 // Gone. 493 dismissCallbackRegistry.notifyDismissSucceeded() 494 // if transition is a scene change, take the destination scene 495 val targetScene = renderedScenes.last() 496 if ( 497 targetScene == Scenes.Lockscreen || 498 !statusBarStateController.leaveOpenOnKeyguardHide() 499 ) { 500 Scenes.Gone to 501 "device was unlocked with bouncer showing and shade" + 502 " didn't need to be left open" 503 } else { 504 if (previousScene.value != Scenes.Gone) { 505 replaceLockscreenSceneOnBackStack() 506 } 507 targetScene to 508 "device was unlocked with primary bouncer showing," + 509 " from sceneKey=$targetScene" 510 } 511 } 512 isOnLockscreen -> 513 // The lockscreen should be dismissed automatically in 2 scenarios: 514 // 1. When face auth bypass is enabled and authentication happens while 515 // the user is on the lockscreen. 516 // 2. Whenever the user authenticates using an active authentication 517 // mechanism like fingerprint auth. Since canSwipeToEnter is true 518 // when the user is passively authenticated, the false value here 519 // when the unlock state changes indicates this is an active 520 // authentication attempt. 521 when { 522 deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == 523 true -> 524 Scenes.Gone to 525 "device has been unlocked on lockscreen with bypass " + 526 "enabled or using an active authentication " + 527 "mechanism: ${deviceUnlockStatus.deviceUnlockSource}" 528 else -> null 529 } 530 // Not on lockscreen or bouncer, so remain in the current scene but since 531 // unlocked, replace the Lockscreen scene from the bottom of the navigation 532 // back stack with the Gone scene. 533 else -> { 534 replaceLockscreenSceneOnBackStack() 535 null 536 } 537 } 538 } 539 .collect { (targetSceneKey, loggingReason) -> 540 switchToScene(targetSceneKey = targetSceneKey, loggingReason = loggingReason) 541 } 542 } 543 } 544 545 /** 546 * If the [Scenes.Lockscreen] is on the bottom of the navigation backstack, replaces it with 547 * [Scenes.Gone]. 548 */ 549 private fun replaceLockscreenSceneOnBackStack() { 550 sceneBackInteractor.updateBackStack { stack -> 551 val list = stack.asIterable().toMutableList() 552 if (list.lastOrNull() == Scenes.Lockscreen) { 553 list[list.size - 1] = Scenes.Gone 554 sceneStackOf(*list.toTypedArray()) 555 } else { 556 stack 557 } 558 } 559 } 560 561 private fun handlePowerState() { 562 applicationScope.launch { 563 powerInteractor.detailedWakefulness.collect { wakefulness -> 564 // Detect a double-tap-power-button gesture that was started while the device was 565 // still awake. 566 if (wakefulness.isAsleep()) return@collect 567 if (!wakefulness.powerButtonLaunchGestureTriggered) return@collect 568 if (wakefulness.lastSleepReason != WakeSleepReason.POWER_BUTTON) return@collect 569 570 // If we're mid-transition from Gone to Lockscreen due to the first power button 571 // press, then return to Gone. 572 val transition: ObservableTransitionState.Transition = 573 sceneInteractor.transitionState.value as? ObservableTransitionState.Transition 574 ?: return@collect 575 if ( 576 transition.fromContent == Scenes.Gone && 577 transition.toContent == Scenes.Lockscreen 578 ) { 579 switchToScene( 580 targetSceneKey = Scenes.Gone, 581 loggingReason = "double-tap power gesture", 582 ) 583 } 584 } 585 } 586 applicationScope.launch { 587 powerInteractor.isAsleep.collect { isAsleep -> 588 if (isAsleep) { 589 alternateBouncerInteractor.hide() 590 dismissCallbackRegistry.notifyDismissCancelled() 591 592 switchToScene( 593 targetSceneKey = Scenes.Lockscreen, 594 loggingReason = "device is starting to sleep", 595 sceneState = keyguardInteractor.asleepKeyguardState.value, 596 freezeAndAnimateToCurrentState = true, 597 ) 598 } else { 599 val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value 600 val isUnlocked = deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked 601 if (isUnlocked && canSwipeToEnter == false) { 602 val isTransitioningToLockscreen = 603 sceneInteractor.transitioningTo.value == Scenes.Lockscreen 604 if (!isTransitioningToLockscreen) { 605 switchToScene( 606 targetSceneKey = Scenes.Gone, 607 loggingReason = 608 "device is waking up while unlocked without the ability to" + 609 " swipe up on lockscreen to enter and not on or" + 610 " transitioning to, the lockscreen scene.", 611 ) 612 } 613 } else if ( 614 authenticationInteractor.get().getAuthenticationMethod() == 615 AuthenticationMethodModel.Sim 616 ) { 617 sceneInteractor.showOverlay( 618 overlay = Overlays.Bouncer, 619 loggingReason = "device is starting to wake up with a locked sim", 620 ) 621 } 622 } 623 } 624 } 625 } 626 627 private fun handleDreamState() { 628 applicationScope.launch { 629 keyguardInteractor.isAbleToDream 630 .sample(sceneInteractor.transitionState, ::Pair) 631 .collect { (isAbleToDream, transitionState) -> 632 if (transitionState.isIdle(Scenes.Communal)) { 633 // The dream is automatically started underneath the hub, don't transition 634 // to dream when this is happening as communal is still visible on top. 635 return@collect 636 } 637 if (isAbleToDream) { 638 switchToScene( 639 targetSceneKey = Scenes.Dream, 640 loggingReason = "dream started", 641 ) 642 } else { 643 switchToScene( 644 targetSceneKey = SceneFamilies.Home, 645 loggingReason = "dream stopped", 646 ) 647 } 648 } 649 } 650 } 651 652 private fun handleShadeTouchability() { 653 applicationScope.launch { 654 repeatWhen(deviceEntryInteractor.isDeviceEntered.map { !it }) { 655 // Run logic only when the device isn't entered. 656 repeatWhen( 657 sceneInteractor.transitionState.map { !it.isTransitioning(to = Scenes.Gone) } 658 ) { 659 // Run logic only when not transitioning to gone. 660 shadeInteractor.isShadeTouchable 661 .distinctUntilChanged() 662 .filter { !it } 663 .collect { 664 switchToScene( 665 targetSceneKey = Scenes.Lockscreen, 666 loggingReason = 667 "device became non-interactive (SceneContainerStartable)", 668 ) 669 } 670 } 671 } 672 } 673 } 674 675 private fun handleDisableFlags() { 676 applicationScope.launch { 677 launch { 678 sceneInteractor.currentScene.collectLatest { currentScene -> 679 disabledContentInteractor.repeatWhenDisabled(currentScene) { 680 switchToScene( 681 targetSceneKey = SceneFamilies.Home, 682 loggingReason = 683 "Current scene ${currentScene.debugName} became" + " disabled", 684 ) 685 } 686 } 687 } 688 689 launch { 690 sceneInteractor.currentOverlays.collectLatest { overlays -> 691 overlays.forEach { overlay -> 692 launch { 693 disabledContentInteractor.repeatWhenDisabled(overlay) { 694 sceneInteractor.hideOverlay( 695 overlay = overlay, 696 loggingReason = 697 "Overlay ${overlay.debugName} became" + " disabled", 698 ) 699 } 700 } 701 } 702 } 703 } 704 } 705 } 706 707 private fun handleDeviceEntryHapticsWhileDeviceLocked() { 708 applicationScope.launch { 709 deviceEntryInteractor.isDeviceEntered.collectLatest { isDeviceEntered -> 710 // Only check for haptics signals before device is entered 711 if (!isDeviceEntered) { 712 coroutineScope { 713 launch { 714 deviceEntryHapticsInteractor.playSuccessHapticOnDeviceEntry 715 .sample(sceneInteractor.currentScene) 716 .collect { currentScene -> 717 if (Flags.msdlFeedback()) { 718 msdlPlayer.playToken( 719 MSDLToken.UNLOCK, 720 authInteractionProperties, 721 ) 722 } else { 723 vibratorHelper.vibrateAuthSuccess( 724 "$TAG, $currentScene device-entry::success" 725 ) 726 } 727 } 728 } 729 730 launch { 731 deviceEntryHapticsInteractor.playErrorHaptic 732 .sample(sceneInteractor.currentScene) 733 .collect { currentScene -> 734 if (Flags.msdlFeedback()) { 735 msdlPlayer.playToken( 736 MSDLToken.FAILURE, 737 authInteractionProperties, 738 ) 739 } else { 740 vibratorHelper.vibrateAuthError( 741 "$TAG, $currentScene device-entry::error" 742 ) 743 } 744 } 745 } 746 } 747 } 748 } 749 } 750 } 751 752 /** Keeps [SysUiState] up-to-date */ 753 private fun hydrateSystemUiState() { 754 applicationScope.launch { 755 combine( 756 sceneInteractor.transitionState 757 .mapNotNull { it as? ObservableTransitionState.Idle } 758 .distinctUntilChanged(), 759 sceneInteractor.isVisible, 760 occlusionInteractor.invisibleDueToOcclusion, 761 ) { idleState, isVisible, invisibleDueToOcclusion -> 762 SceneContainerPlugin.SceneContainerPluginState( 763 scene = idleState.currentScene, 764 overlays = idleState.currentOverlays, 765 isVisible = isVisible, 766 invisibleDueToOcclusion = invisibleDueToOcclusion, 767 ) 768 } 769 .map { sceneContainerPluginState -> 770 SceneContainerPlugin.EvaluatorByFlag.map { (flag, evaluator) -> 771 flag to evaluator(sceneContainerPluginState) 772 } 773 .toMap() 774 } 775 .distinctUntilChanged() 776 .collect { flags -> 777 sysUiState.updateFlags( 778 *(flags.entries.map { (key, value) -> key to value }).toTypedArray() 779 ) 780 } 781 } 782 } 783 784 private fun hydrateWindowController() { 785 applicationScope.launch { 786 sceneInteractor.transitionState 787 .mapNotNull { transitionState -> 788 (transitionState as? ObservableTransitionState.Idle)?.currentScene 789 } 790 .distinctUntilChanged() 791 .collect { sceneKey -> 792 windowController.setNotificationShadeFocusable(sceneKey != Scenes.Gone) 793 } 794 } 795 796 applicationScope.launch { 797 deviceEntryInteractor.isDeviceEntered.collect { isDeviceEntered -> 798 windowController.setKeyguardShowing(!isDeviceEntered) 799 } 800 } 801 802 applicationScope.launch { 803 occlusionInteractor.invisibleDueToOcclusion.collect { invisibleDueToOcclusion -> 804 windowController.setKeyguardOccluded(invisibleDueToOcclusion) 805 } 806 } 807 } 808 809 /** Collects and reports signals into the falsing system. */ 810 private fun collectFalsingSignals() { 811 applicationScope.launch { 812 deviceEntryInteractor.isDeviceEntered.collect { isLockscreenDismissed -> 813 if (isLockscreenDismissed) { 814 falsingCollector.onSuccessfulUnlock() 815 } 816 } 817 } 818 819 applicationScope.launch { 820 keyguardInteractor.isDozing.collect { isDozing -> 821 falsingCollector.setShowingAod(isDozing) 822 } 823 } 824 825 applicationScope.launch { 826 powerInteractor.detailedWakefulness 827 .distinctUntilChangedBy { it.isAwake() } 828 .collect { wakefulness -> 829 when { 830 wakefulness.isAwakeFromTouch() -> falsingCollector.onScreenOnFromTouch() 831 wakefulness.isAwake() -> falsingCollector.onScreenTurningOn() 832 wakefulness.isAsleep() -> falsingCollector.onScreenOff() 833 } 834 } 835 } 836 837 applicationScope.launch { 838 sceneInteractor.currentOverlays 839 .map { Overlays.Bouncer in it } 840 .distinctUntilChanged() 841 .collect { switchedToBouncerOverlay -> 842 if (switchedToBouncerOverlay) { 843 falsingCollector.onBouncerShown() 844 } else { 845 falsingCollector.onBouncerHidden() 846 } 847 } 848 } 849 } 850 851 /** Switches to the lockscreen when falsing is detected. */ 852 private fun respondToFalsingDetections() { 853 applicationScope.launch { 854 conflatedCallbackFlow { 855 val listener = FalsingBeliefListener { trySend(Unit) } 856 falsingManager.addFalsingBeliefListener(listener) 857 awaitClose { falsingManager.removeFalsingBeliefListener(listener) } 858 } 859 .collect { 860 val loggingReason = "Falsing detected." 861 switchToScene(targetSceneKey = Scenes.Lockscreen, loggingReason = loggingReason) 862 } 863 } 864 } 865 866 /** Keeps the interaction state of [CentralSurfaces] up-to-date. */ 867 private fun hydrateInteractionState() { 868 applicationScope.launch { 869 deviceUnlockedInteractor.deviceUnlockStatus 870 .map { !it.isUnlocked } 871 .flatMapLatest { isDeviceLocked -> 872 if (isDeviceLocked) { 873 sceneInteractor.transitionState 874 .mapNotNull { it as? ObservableTransitionState.Idle } 875 .map { it.currentScene to it.currentOverlays } 876 .distinctUntilChanged() 877 .map { (currentScene, currentOverlays) -> 878 when { 879 // When locked, showing the lockscreen scene should be reported 880 // as "interacting" while showing other scenes should report as 881 // "not interacting". 882 // 883 // This is done here in order to match the legacy 884 // implementation. The real reason why is lost to lore and myth. 885 Overlays.NotificationsShade in currentOverlays -> false 886 Overlays.QuickSettingsShade in currentOverlays -> null 887 Overlays.Bouncer in currentOverlays -> false 888 currentScene == Scenes.Lockscreen -> true 889 currentScene == Scenes.Shade -> false 890 else -> null 891 } 892 } 893 } else { 894 flowOf(null) 895 } 896 } 897 .collect { isInteractingOrNull -> 898 isInteractingOrNull?.let { isInteracting -> 899 centralSurfaces?.setInteracting( 900 StatusBarManager.WINDOW_STATUS_BAR, 901 isInteracting, 902 ) 903 } 904 } 905 } 906 } 907 908 private fun handleBouncerOverscroll() { 909 applicationScope.launch { 910 sceneInteractor.transitionState 911 // Only consider transitions. 912 .filterIsInstance<ObservableTransitionState.Transition>() 913 // Only consider user-initiated (e.g. drags) that go from bouncer to lockscreen. 914 .filter { transition -> 915 transition.fromContent == Overlays.Bouncer && 916 transition.toContent == Scenes.Lockscreen && 917 transition.isInitiatedByUserInput 918 } 919 .flatMapLatest { it.progress } 920 // Figure out the direction of scrolling. 921 .map { progress -> 922 when { 923 progress > 0 -> 1 924 progress < 0 -> -1 925 else -> 0 926 } 927 } 928 .distinctUntilChanged() 929 // Only consider negative scrolling, AKA overscroll. 930 .filter { it == -1 } 931 .collect { faceUnlockInteractor.onSwipeUpOnBouncer() } 932 } 933 } 934 935 private fun handleKeyguardEnabledness() { 936 // Automatically switches scenes when keyguard is enabled or disabled, as needed. 937 applicationScope.launch { 938 keyguardEnabledInteractor.isKeyguardEnabled 939 .sample( 940 combine( 941 deviceUnlockedInteractor.isInLockdown, 942 deviceEntryInteractor.isDeviceEntered, 943 ::Pair, 944 ) 945 ) { isKeyguardEnabled, (isInLockdown, isDeviceEntered) -> 946 when { 947 !isKeyguardEnabled && !isInLockdown && !isDeviceEntered -> { 948 keyguardEnabledInteractor.setShowKeyguardWhenReenabled(true) 949 Scenes.Gone to "Keyguard became disabled" 950 } 951 isKeyguardEnabled && 952 keyguardEnabledInteractor.isShowKeyguardWhenReenabled() -> { 953 keyguardEnabledInteractor.setShowKeyguardWhenReenabled(false) 954 Scenes.Lockscreen to "Keyguard became enabled" 955 } 956 else -> null 957 } 958 } 959 .filterNotNull() 960 .collect { (targetScene, loggingReason) -> 961 switchToScene(targetScene, loggingReason) 962 } 963 } 964 965 // Clears the showKeyguardWhenReenabled if the auth method changes to an insecure one. 966 applicationScope.launch { 967 authenticationInteractor 968 .get() 969 .authenticationMethod 970 .map { it.isSecure } 971 .distinctUntilChanged() 972 .collect { isAuthenticationMethodSecure -> 973 if (!isAuthenticationMethodSecure) { 974 keyguardEnabledInteractor.setShowKeyguardWhenReenabled(false) 975 } 976 } 977 } 978 } 979 980 private fun switchToScene( 981 targetSceneKey: SceneKey, 982 loggingReason: String, 983 sceneState: Any? = null, 984 freezeAndAnimateToCurrentState: Boolean = false, 985 ) { 986 sceneInteractor.changeScene( 987 toScene = targetSceneKey, 988 loggingReason = loggingReason, 989 sceneState = sceneState, 990 forceSettleToTargetScene = freezeAndAnimateToCurrentState, 991 ) 992 } 993 994 private fun hydrateBackStack() { 995 applicationScope.launch { 996 sceneInteractor.currentScene.pairwise().collect { (from, to) -> 997 sceneBackInteractor.onSceneChange(from = from, to = to) 998 } 999 } 1000 } 1001 1002 private fun notifyKeyguardDismissCancelledCallbacks() { 1003 applicationScope.launch { 1004 combine(deviceEntryInteractor.isUnlocked, sceneInteractor.currentOverlays.pairwise()) { 1005 isUnlocked, 1006 overlayChange -> 1007 val difference = overlayChange.previousValue - overlayChange.newValue 1008 !isUnlocked && 1009 sceneInteractor.currentScene.value != Scenes.Gone && 1010 Overlays.Bouncer in difference 1011 } 1012 .collect { notifyKeyguardDismissCancelled -> 1013 if (notifyKeyguardDismissCancelled) { 1014 dismissCallbackRegistry.notifyDismissCancelled() 1015 } 1016 } 1017 } 1018 } 1019 1020 /** 1021 * Keeps the value of [DeviceEntryInteractor.isLockscreenEnabled] fresh. 1022 * 1023 * This is needed because that value is sourced from a non-observable data source 1024 * (`LockPatternUtils`, which doesn't expose a listener or callback for this value). Therefore, 1025 * every time a transition to the `Lockscreen` scene is started, the value is re-fetched and 1026 * cached. 1027 */ 1028 private fun refreshLockscreenEnabled() { 1029 applicationScope.launch { 1030 sceneInteractor.transitionState 1031 .map { it.isTransitioning(to = Scenes.Lockscreen) } 1032 .distinctUntilChanged() 1033 .filter { it } 1034 .collectLatest { deviceEntryInteractor.refreshLockscreenEnabled() } 1035 } 1036 } 1037 1038 /** 1039 * Wires the scene framework to activity transition animations that originate from anywhere. A 1040 * subset of these may actually originate from UI inside one of the scenes in the framework. 1041 * 1042 * Telling the scene framework about ongoing activity transition animations is critical so the 1043 * scene framework doesn't make its scene container invisible during a transition. 1044 * 1045 * As it turns out, making the scene container view invisible during a transition animation 1046 * disrupts the animation and causes interaction jank CUJ tracking to ignore reports of the CUJ 1047 * ending or being canceled. 1048 */ 1049 private fun hydrateActivityTransitionAnimationState() { 1050 activityTransitionAnimator.addListener( 1051 object : ActivityTransitionAnimator.Listener { 1052 override fun onTransitionAnimationStart() { 1053 sceneInteractor.onTransitionAnimationStart() 1054 } 1055 1056 override fun onTransitionAnimationEnd() { 1057 sceneInteractor.onTransitionAnimationEnd() 1058 } 1059 } 1060 ) 1061 } 1062 1063 private fun lockWhenDeviceBecomesUntrusted() { 1064 applicationScope.launch { 1065 trustInteractor.isTrusted.pairwise().collect { (wasTrusted, isTrusted) -> 1066 if (wasTrusted && !isTrusted && !deviceEntryInteractor.isDeviceEntered.value) { 1067 deviceEntryInteractor.lockNow( 1068 "Exited trusted environment while not device not entered" 1069 ) 1070 } 1071 } 1072 } 1073 } 1074 1075 private suspend fun repeatWhen(condition: Flow<Boolean>, block: suspend () -> Unit) { 1076 condition.distinctUntilChanged().collectLatest { conditionMet -> 1077 if (conditionMet) { 1078 block() 1079 } 1080 } 1081 } 1082 1083 companion object { 1084 private const val TAG = "SceneContainerStartable" 1085 } 1086 } 1087