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.ObservableTransitionState
20 import com.android.compose.animation.scene.SceneKey
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.Scenes
27 import javax.inject.Inject
28 import kotlinx.coroutines.CoroutineScope
29 import kotlinx.coroutines.flow.SharingStarted
30 import kotlinx.coroutines.flow.StateFlow
31 import kotlinx.coroutines.flow.combine
32 import kotlinx.coroutines.flow.map
33 import kotlinx.coroutines.flow.onStart
34 import kotlinx.coroutines.flow.stateIn
35 
36 /** Encapsulates logic regarding the occlusion state of the scene container. */
37 @SysUISingleton
38 class SceneContainerOcclusionInteractor
39 @Inject
40 constructor(
41     @Application applicationScope: CoroutineScope,
42     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
43     sceneInteractor: SceneInteractor,
44     keyguardTransitionInteractor: KeyguardTransitionInteractor,
45 ) {
46     /** Whether a show-when-locked activity is at the top of the current activity stack. */
47     private val isOccludingActivityShown: StateFlow<Boolean> =
48         keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop.stateIn(
49             scope = applicationScope,
50             started = SharingStarted.WhileSubscribed(),
51             initialValue = false,
52         )
53 
54     /**
55      * Whether AOD is fully shown (not transitioning) or partially shown during a transition to/from
56      * AOD.
57      */
58     private val isAodFullyOrPartiallyShown: StateFlow<Boolean> =
59         keyguardTransitionInteractor
60             .transitionValue(KeyguardState.AOD)
61             .onStart { emit(0f) }
62             .map { it > 0 }
63             .stateIn(
64                 scope = applicationScope,
65                 started = SharingStarted.WhileSubscribed(),
66                 initialValue = false,
67             )
68 
69     /**
70      * Whether the scene container should become invisible due to "occlusion" by an in-foreground
71      * "show when locked" activity.
72      */
73     val invisibleDueToOcclusion: StateFlow<Boolean> =
74         combine(
75                 isOccludingActivityShown,
76                 sceneInteractor.transitionState,
77                 isAodFullyOrPartiallyShown,
78             ) { isOccludingActivityShown, sceneTransitionState, isAodFullyOrPartiallyShown ->
79                 invisibleDueToOcclusion(
80                     isOccludingActivityShown = isOccludingActivityShown,
81                     sceneTransitionState = sceneTransitionState,
82                     isAodFullyOrPartiallyShown = isAodFullyOrPartiallyShown,
83                 )
84             }
85             .stateIn(
86                 scope = applicationScope,
87                 started = SharingStarted.WhileSubscribed(),
88                 initialValue =
89                     invisibleDueToOcclusion(
90                         isOccludingActivityShown = isOccludingActivityShown.value,
91                         sceneTransitionState = sceneInteractor.transitionState.value,
92                         isAodFullyOrPartiallyShown = isAodFullyOrPartiallyShown.value,
93                     ),
94             )
95 
96     private fun invisibleDueToOcclusion(
97         isOccludingActivityShown: Boolean,
98         sceneTransitionState: ObservableTransitionState,
99         isAodFullyOrPartiallyShown: Boolean,
100     ): Boolean {
101         return isOccludingActivityShown &&
102             // Cannot be occluded in AOD.
103             !isAodFullyOrPartiallyShown &&
104             // Only some scenes can be occluded.
105             sceneTransitionState.canBeOccluded
106     }
107 
108     private val ObservableTransitionState.canBeOccluded: Boolean
109         get() =
110             when (this) {
111                 is ObservableTransitionState.Idle -> currentScene.canBeOccluded
112                 is ObservableTransitionState.Transition ->
113                     fromScene.canBeOccluded && toScene.canBeOccluded
114             }
115 
116     /**
117      * Whether the scene can be occluded by a "show when locked" activity. Some scenes should, on
118      * principle not be occlude-able because they render as if they are expanding on top of the
119      * occluding activity.
120      */
121     private val SceneKey.canBeOccluded: Boolean
122         get() =
123             when (this) {
124                 Scenes.Bouncer -> true
125                 Scenes.Communal -> true
126                 Scenes.Gone -> true
127                 Scenes.Lockscreen -> true
128                 Scenes.NotificationsShade -> false
129                 Scenes.QuickSettings -> false
130                 Scenes.QuickSettingsShade -> false
131                 Scenes.Shade -> false
132                 else -> error("SceneKey \"$this\" doesn't have a mapping for canBeOccluded!")
133             }
134 }
135