• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 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.shade.domain.interactor
18 
19 import android.util.Log
20 import com.android.systemui.dagger.SysUISingleton
21 import com.android.systemui.dagger.qualifiers.Background
22 import com.android.systemui.scene.shared.flag.SceneContainerFlag
23 import com.android.systemui.shade.ShadeTraceLogger.traceWaitForExpansion
24 import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement
25 import com.android.systemui.util.kotlin.Utils.Companion.combineState
26 import javax.inject.Inject
27 import kotlin.coroutines.CoroutineContext
28 import kotlin.time.Duration
29 import kotlin.time.Duration.Companion.seconds
30 import kotlinx.coroutines.CoroutineScope
31 import kotlinx.coroutines.flow.MutableStateFlow
32 import kotlinx.coroutines.flow.SharingStarted
33 import kotlinx.coroutines.flow.StateFlow
34 import kotlinx.coroutines.flow.first
35 import kotlinx.coroutines.withContext
36 import kotlinx.coroutines.withTimeoutOrNull
37 
38 /**
39  * Wrapper around [ShadeInteractor] to facilitate expansion and collapse of Notifications and quick
40  * settings.
41  *
42  * Specifically created to simplify [ShadeDisplaysInteractor] logic.
43  *
44  * NOTE: with [SceneContainerFlag] disabled, [currentlyExpandedElement] will always return `null`!
45  */
46 interface ShadeExpandedStateInteractor {
47     /** Returns the expanded [ShadeElement]. If none is, returns null. */
48     val currentlyExpandedElement: StateFlow<ShadeElement?>
49 
50     /** An element from the shade window that can be expanded or collapsed. */
51     sealed class ShadeElement {
52         /** Expands the shade element, returning when the expansion is done */
53         abstract suspend fun expand(reason: String)
54 
55         /** Collapses the shade element, returning when the collapse is done. */
56         abstract suspend fun collapse(reason: String)
57     }
58 }
59 
60 private val EXPAND_COLLAPSE_TIMEOUT: Duration = 1.seconds
61 
62 @SysUISingleton
63 class ShadeExpandedStateInteractorImpl
64 @Inject
65 constructor(
66     shadeInteractor: ShadeInteractor,
67     @Background private val bgScope: CoroutineScope,
68     private val notificationElement: NotificationShadeElement,
69     private val qsElement: QSShadeElement,
70 ) : ShadeExpandedStateInteractor {
71 
72     override val currentlyExpandedElement: StateFlow<ShadeElement?> =
73         if (SceneContainerFlag.isEnabled) {
74             combineState(
75                 shadeInteractor.isShadeAnyExpanded,
76                 shadeInteractor.isQsExpanded,
77                 bgScope,
78                 SharingStarted.Eagerly,
isQsExpandednull79             ) { isShadeAnyExpanded, isQsExpanded ->
80                 when {
81                     isShadeAnyExpanded -> notificationElement
82                     isQsExpanded -> qsElement
83                     else -> null
84                 }
85             }
86         } else {
87             MutableStateFlow(null)
88         }
89 }
90 
waitUntilnull91 private suspend fun StateFlow<Float>.waitUntil(f: Float, coroutineContext: CoroutineContext) {
92     // it's important to not do this in the main thread otherwise it will block any rendering.
93     withContext(coroutineContext) {
94         withTimeoutOrNull(EXPAND_COLLAPSE_TIMEOUT) {
95             traceWaitForExpansion(expansion = f) { first { it == f } }
96         }
97             ?: Log.e(
98                 "ShadeExpStateInteractor",
99                 "Timed out after ${EXPAND_COLLAPSE_TIMEOUT.inWholeMilliseconds}ms while waiting " +
100                     "for expansion to match $f. Current one: $value",
101             )
102     }
103 }
104 
105 @SysUISingleton
106 class NotificationShadeElement
107 @Inject
108 constructor(
109     private val shadeInteractor: ShadeInteractor,
110     @Background private val bgContext: CoroutineContext,
111 ) : ShadeElement() {
expandnull112     override suspend fun expand(reason: String) {
113         if (SceneContainerFlag.isEnabled) {
114             shadeInteractor.expandNotificationsShade(reason)
115             shadeInteractor.shadeExpansion.waitUntil(1f, bgContext)
116         }
117     }
118 
collapsenull119     override suspend fun collapse(reason: String) {
120         if (SceneContainerFlag.isEnabled) {
121             shadeInteractor.collapseNotificationsShade(reason)
122             shadeInteractor.shadeExpansion.waitUntil(0f, bgContext)
123         }
124     }
125 }
126 
127 @SysUISingleton
128 class QSShadeElement
129 @Inject
130 constructor(
131     private val shadeInteractor: ShadeInteractor,
132     @Background private val bgContext: CoroutineContext,
133 ) : ShadeElement() {
expandnull134     override suspend fun expand(reason: String) {
135         if (SceneContainerFlag.isEnabled) {
136             shadeInteractor.expandQuickSettingsShade(reason)
137             shadeInteractor.qsExpansion.waitUntil(1f, bgContext)
138         }
139     }
140 
collapsenull141     override suspend fun collapse(reason: String) {
142         if (SceneContainerFlag.isEnabled) {
143             shadeInteractor.collapseQuickSettingsShade(reason)
144             shadeInteractor.qsExpansion.waitUntil(0f, bgContext)
145         }
146     }
147 }
148