• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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