• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 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 
18 package com.android.systemui.statusbar.notification.stack.domain.interactor
19 
20 import com.android.systemui.dagger.SysUISingleton
21 import com.android.systemui.scene.domain.interactor.SceneInteractor
22 import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
23 import com.android.systemui.shade.shared.model.ShadeMode
24 import com.android.systemui.statusbar.notification.stack.data.repository.NotificationPlaceholderRepository
25 import com.android.systemui.statusbar.notification.stack.data.repository.NotificationViewHeightRepository
26 import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent
27 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
28 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
29 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
30 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState
31 import java.util.function.Consumer
32 import javax.inject.Inject
33 import kotlinx.coroutines.flow.Flow
34 import kotlinx.coroutines.flow.StateFlow
35 import kotlinx.coroutines.flow.asStateFlow
36 import kotlinx.coroutines.flow.combine
37 import kotlinx.coroutines.flow.distinctUntilChanged
38 import kotlinx.coroutines.flow.flowOf
39 
40 /** An interactor which controls the appearance of the NSSL */
41 @SysUISingleton
42 class NotificationStackAppearanceInteractor
43 @Inject
44 constructor(
45     private val viewHeightRepository: NotificationViewHeightRepository,
46     private val placeholderRepository: NotificationPlaceholderRepository,
47     sceneInteractor: SceneInteractor,
48     shadeModeInteractor: ShadeModeInteractor,
49 ) {
50     /** The bounds of the notification stack in the current scene. */
51     val notificationShadeScrimBounds: StateFlow<ShadeScrimBounds?> =
52         placeholderRepository.notificationShadeScrimBounds.asStateFlow()
53 
54     /**
55      * Whether the stack is expanding from GONE-with-HUN to SHADE
56      *
57      * TODO(b/296118689): implement this to match legacy QSController logic
58      */
59     private val isExpandingFromHeadsUp: Flow<Boolean> = flowOf(false)
60 
61     /** The rounding of the notification stack. */
62     val shadeScrimRounding: Flow<ShadeScrimRounding> =
63         combine(shadeModeInteractor.shadeMode, isExpandingFromHeadsUp) {
64                 shadeMode,
65                 isExpandingFromHeadsUp ->
66                 ShadeScrimRounding(
67                     isTopRounded = !(shadeMode == ShadeMode.Split && isExpandingFromHeadsUp),
68                     isBottomRounded = shadeMode != ShadeMode.Single,
69                 )
70             }
71             .distinctUntilChanged()
72 
73     /** The alpha of the Notification Stack for the brightness mirror */
74     val alphaForBrightnessMirror: StateFlow<Float> =
75         placeholderRepository.alphaForBrightnessMirror.asStateFlow()
76 
77     /** The alpha of the Notification Stack for lockscreen fade-in */
78     val alphaForLockscreenFadeIn: StateFlow<Float> =
79         placeholderRepository.alphaForLockscreenFadeIn.asStateFlow()
80 
81     /** The height of the keyguard's available space bounds */
82     val constrainedAvailableSpace: StateFlow<Int> =
83         placeholderRepository.constrainedAvailableSpace.asStateFlow()
84 
85     /** Scroll state of the notification shade. */
86     val shadeScrollState: StateFlow<ShadeScrollState> =
87         placeholderRepository.shadeScrollState.asStateFlow()
88 
89     /**
90      * The amount in px that the notification stack should scroll due to internal expansion. This
91      * should only happen when a notification expansion hits the bottom of the screen, so it is
92      * necessary to scroll up to keep expanding the notification.
93      */
94     val syntheticScroll: Flow<Float> = viewHeightRepository.syntheticScroll.asStateFlow()
95 
96     /**
97      * Whether the current touch gesture is overscroll. If true, it means the NSSL has already
98      * consumed part of the gesture.
99      */
100     val isCurrentGestureOverscroll: Flow<Boolean> =
101         viewHeightRepository.isCurrentGestureOverscroll.asStateFlow()
102 
103     /** Whether we should close any notification guts that are currently open. */
104     val shouldCloseGuts: Flow<Boolean> =
105         combine(
106             sceneInteractor.isSceneContainerUserInputOngoing,
107             viewHeightRepository.isCurrentGestureInGuts,
108         ) { isUserInputOngoing, isCurrentGestureInGuts ->
109             isUserInputOngoing && !isCurrentGestureInGuts
110         }
111 
112     /** Sets the alpha to apply to the NSSL for the brightness mirror */
113     fun setAlphaForBrightnessMirror(alpha: Float) {
114         placeholderRepository.alphaForBrightnessMirror.value = alpha
115     }
116 
117     /** Sets the alpha to apply to the NSSL for fade-in on lockscreen */
118     fun setAlphaForLockscreenFadeIn(alpha: Float) {
119         placeholderRepository.alphaForLockscreenFadeIn.value = alpha
120     }
121 
122     /** Sets the position of the notification stack in the current scene. */
123     fun setNotificationShadeScrimBounds(bounds: ShadeScrimBounds?) {
124         checkValidBounds(bounds)
125         placeholderRepository.notificationShadeScrimBounds.value = bounds
126     }
127 
128     /**
129      * Sends the bounds of the QuickSettings panel to the consumer set by [setQsPanelShapeConsumer].
130      *
131      * Used to clip Notification content when the QuickSettings Overlay panel covers it. Sending
132      * `null` resets the negative shape clipping of the Notification Stack.
133      */
134     fun sendQsPanelShape(shape: ShadeScrimShape?) {
135         checkValidBounds(shape?.bounds)
136         placeholderRepository.qsPanelShapeConsumer?.invoke(shape)
137     }
138 
139     /**
140      * Sets a consumer to be notified when the QuickSettings Overlay panel changes size or position.
141      */
142     fun setQsPanelShapeConsumer(consumer: ((ShadeScrimShape?) -> Unit)?) {
143         placeholderRepository.qsPanelShapeConsumer = consumer
144     }
145 
146     /** Updates the current scroll state of the notification shade. */
147     fun setScrollState(shadeScrollState: ShadeScrollState) {
148         placeholderRepository.shadeScrollState.value = shadeScrollState
149     }
150 
151     /** Sets the amount (px) that the notification stack should scroll due to internal expansion. */
152     fun setSyntheticScroll(delta: Float) {
153         viewHeightRepository.syntheticScroll.value = delta
154     }
155 
156     /** Sends an [AccessibilityScrollEvent] to scroll the stack up or down. */
157     fun sendAccessibilityScrollEvent(accessibilityScrollEvent: AccessibilityScrollEvent) {
158         placeholderRepository.accessibilityScrollEventConsumer?.accept(accessibilityScrollEvent)
159     }
160 
161     /** Set a consumer for the [AccessibilityScrollEvent]s to be handled by the placeholder. */
162     fun setAccessibilityScrollEventConsumer(consumer: Consumer<AccessibilityScrollEvent>?) {
163         placeholderRepository.accessibilityScrollEventConsumer = consumer
164     }
165 
166     /** Sets whether the current touch gesture is overscroll. */
167     fun setCurrentGestureOverscroll(isOverscroll: Boolean) {
168         viewHeightRepository.isCurrentGestureOverscroll.value = isOverscroll
169     }
170 
171     fun setCurrentGestureInGuts(isInGuts: Boolean) {
172         viewHeightRepository.isCurrentGestureInGuts.value = isInGuts
173     }
174 
175     fun setConstrainedAvailableSpace(height: Int) {
176         placeholderRepository.constrainedAvailableSpace.value = height
177     }
178 
179     private fun checkValidBounds(bounds: ShadeScrimBounds?) {
180         check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
181     }
182 }
183