1 /* <lambda>null2 * Copyright (C) 2024 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.interactor 18 19 import com.android.compose.animation.scene.ContentKey 20 import com.android.compose.animation.scene.ObservableTransitionState 21 import com.android.systemui.dagger.SysUISingleton 22 import com.android.systemui.dagger.qualifiers.Application 23 import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor 24 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 25 import com.android.systemui.keyguard.shared.model.KeyguardState 26 import com.android.systemui.scene.shared.model.Overlays 27 import com.android.systemui.scene.shared.model.Scenes 28 import javax.inject.Inject 29 import kotlinx.coroutines.CoroutineScope 30 import kotlinx.coroutines.flow.SharingStarted 31 import kotlinx.coroutines.flow.StateFlow 32 import kotlinx.coroutines.flow.combine 33 import kotlinx.coroutines.flow.map 34 import kotlinx.coroutines.flow.onStart 35 import kotlinx.coroutines.flow.stateIn 36 37 /** Encapsulates logic regarding the occlusion state of the scene container. */ 38 @SysUISingleton 39 class SceneContainerOcclusionInteractor 40 @Inject 41 constructor( 42 @Application applicationScope: CoroutineScope, 43 keyguardOcclusionInteractor: KeyguardOcclusionInteractor, 44 sceneInteractor: SceneInteractor, 45 keyguardTransitionInteractor: KeyguardTransitionInteractor, 46 ) { 47 /** 48 * Whether a show-when-locked activity is at the top of the current activity stack. 49 * 50 * Note: this isn't enough to figure out whether the scene container UI should be invisible as 51 * that also depends on the things like the state of AOD and the current scene. If the code 52 * needs that, [invisibleDueToOcclusion] should be collected instead. 53 */ 54 val isOccludingActivityShown: StateFlow<Boolean> = 55 keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop.stateIn( 56 scope = applicationScope, 57 started = SharingStarted.WhileSubscribed(), 58 initialValue = false, 59 ) 60 61 /** 62 * Whether AOD is fully shown (not transitioning) or partially shown during a transition to/from 63 * AOD. 64 */ 65 private val isAodFullyOrPartiallyShown: StateFlow<Boolean> = 66 keyguardTransitionInteractor 67 .transitionValue(KeyguardState.AOD) 68 .onStart { emit(0f) } 69 .map { it > 0 } 70 .stateIn( 71 scope = applicationScope, 72 started = SharingStarted.WhileSubscribed(), 73 initialValue = false, 74 ) 75 76 /** 77 * Whether the scene container should become invisible due to "occlusion" by an in-foreground 78 * "show when locked" activity. 79 * 80 * Note: this returns `false` when an overlaid scene (like shade or QS) is shown above the 81 * occluding activity. 82 */ 83 val invisibleDueToOcclusion: StateFlow<Boolean> = 84 combine( 85 isOccludingActivityShown, 86 sceneInteractor.transitionState, 87 isAodFullyOrPartiallyShown, 88 ) { isOccludingActivityShown, sceneTransitionState, isAodFullyOrPartiallyShown -> 89 invisibleDueToOcclusion( 90 isOccludingActivityShown = isOccludingActivityShown, 91 sceneTransitionState = sceneTransitionState, 92 isAodFullyOrPartiallyShown = isAodFullyOrPartiallyShown, 93 ) 94 } 95 .stateIn( 96 scope = applicationScope, 97 started = SharingStarted.WhileSubscribed(), 98 initialValue = 99 invisibleDueToOcclusion( 100 isOccludingActivityShown = isOccludingActivityShown.value, 101 sceneTransitionState = sceneInteractor.transitionState.value, 102 isAodFullyOrPartiallyShown = isAodFullyOrPartiallyShown.value, 103 ), 104 ) 105 106 private fun invisibleDueToOcclusion( 107 isOccludingActivityShown: Boolean, 108 sceneTransitionState: ObservableTransitionState, 109 isAodFullyOrPartiallyShown: Boolean, 110 ): Boolean { 111 return isOccludingActivityShown && 112 // Cannot be occluded in AOD. 113 !isAodFullyOrPartiallyShown && 114 // Only some scenes can be occluded. 115 sceneTransitionState.canBeOccluded 116 } 117 118 private val ObservableTransitionState.canBeOccluded: Boolean 119 get() = 120 when (this) { 121 is ObservableTransitionState.Idle -> 122 currentOverlays.all { it.canBeOccluded } && currentScene.canBeOccluded 123 is ObservableTransitionState.Transition -> 124 // TODO(b/356596436): Should also verify currentOverlays.isEmpty(), but 125 // currentOverlays is a Flow and we need a state. 126 fromContent.canBeOccluded && toContent.canBeOccluded 127 } 128 129 /** 130 * Whether the content can be occluded by a "show when locked" activity. Some content should, on 131 * principle not be occlude-able because they render as if they are expanding on top of the 132 * occluding activity. 133 */ 134 private val ContentKey.canBeOccluded: Boolean 135 get() = 136 when (this) { 137 Overlays.NotificationsShade -> false 138 Overlays.QuickSettingsShade -> false 139 Overlays.Bouncer -> false 140 Scenes.Communal -> true 141 Scenes.Dream -> false 142 Scenes.Gone -> true 143 Scenes.Lockscreen -> true 144 Scenes.QuickSettings -> false 145 Scenes.Shade -> false 146 else -> error("ContentKey \"$this\" doesn't have a mapping for canBeOccluded!") 147 } 148 } 149