• 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.statusbar.notification.domain.interactor
18 
19 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
20 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
21 import com.android.systemui.keyguard.shared.model.KeyguardState
22 import com.android.systemui.scene.shared.flag.SceneContainerFlag
23 import com.android.systemui.shade.domain.interactor.ShadeInteractor
24 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
25 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
26 import com.android.systemui.statusbar.notification.domain.model.TopPinnedState
27 import com.android.systemui.statusbar.notification.headsup.PinnedStatus
28 import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
29 import javax.inject.Inject
30 import kotlinx.coroutines.flow.Flow
31 import kotlinx.coroutines.flow.combine
32 import kotlinx.coroutines.flow.distinctUntilChanged
33 import kotlinx.coroutines.flow.flatMapLatest
34 import kotlinx.coroutines.flow.flowOf
35 import kotlinx.coroutines.flow.map
36 
37 class HeadsUpNotificationInteractor
38 @Inject
39 constructor(
40     private val headsUpRepository: HeadsUpRepository,
41     faceAuthInteractor: DeviceEntryFaceAuthInteractor,
42     keyguardTransitionInteractor: KeyguardTransitionInteractor,
43     notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
44     shadeInteractor: ShadeInteractor,
45 ) {
46 
47     /** The top-ranked heads up row, regardless of pinned state */
48     val topHeadsUpRow: Flow<HeadsUpRowKey?> = headsUpRepository.topHeadsUpRow
49 
50     /** The top-ranked heads up row, if that row is pinned */
51     val topHeadsUpRowIfPinned: Flow<HeadsUpRowKey?> =
52         headsUpRepository.topHeadsUpRow
53             .flatMapLatest { repository ->
54                 repository?.pinnedStatus?.map { pinnedStatus ->
55                     repository.takeIf { pinnedStatus.isPinned }
56                 } ?: flowOf(null)
57             }
58             .distinctUntilChanged()
59 
60     private val activeHeadsUpRows: Flow<Set<Pair<HeadsUpRowKey, Boolean>>> by lazy {
61         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
62             flowOf(emptySet())
63         } else {
64             headsUpRepository.activeHeadsUpRows.flatMapLatest { repositories ->
65                 if (repositories.isNotEmpty()) {
66                     val toCombine: List<Flow<Pair<HeadsUpRowRepository, Boolean>>> =
67                         repositories.map { repo ->
68                             repo.pinnedStatus.map { pinnedStatus -> repo to pinnedStatus.isPinned }
69                         }
70                     combine(toCombine) { pairs -> pairs.toSet() }
71                 } else {
72                     // if the set is empty, there are no flows to combine
73                     flowOf(emptySet())
74                 }
75             }
76         }
77     }
78 
79     /** Set of currently active top-level heads up rows to be displayed. */
80     val activeHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
81         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
82             flowOf(emptySet())
83         } else {
84             activeHeadsUpRows.map { it.map { (repo, _) -> repo }.toSet() }.distinctUntilChanged()
85         }
86     }
87 
88     /** Set of currently pinned top-level heads up rows to be displayed. */
89     val pinnedHeadsUpRowKeys: Flow<Set<HeadsUpRowKey>> by lazy {
90         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
91             flowOf(emptySet())
92         } else {
93             activeHeadsUpRows
94                 .map { it.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet() }
95                 .distinctUntilChanged() // TODO(b/402428276) stop sending duplicate updates instead
96         }
97     }
98 
99     /** What [PinnedStatus] and key does the top row have? */
100     private val topPinnedState: Flow<TopPinnedState> =
101         headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
102             if (rows.isNotEmpty()) {
103                 // For each row, emits a (key, pinnedStatus) pair each time any row's
104                 // `pinnedStatus` changes
105                 val toCombine: List<Flow<Pair<String, PinnedStatus>>> =
106                     rows.map { row -> row.pinnedStatus.map { status -> row.key to status } }
107                 combine(toCombine) { pairs ->
108                     val topPinnedRow: Pair<String, PinnedStatus>? =
109                         pairs.firstOrNull { it.second.isPinned }
110                     if (topPinnedRow != null) {
111                         TopPinnedState.Pinned(
112                             key = topPinnedRow.first,
113                             status = topPinnedRow.second,
114                         )
115                     } else {
116                         TopPinnedState.NothingPinned
117                     }
118                 }
119             } else {
120                 flowOf(TopPinnedState.NothingPinned)
121             }
122         }
123 
124     /** Are there any pinned heads up rows to display? */
125     val hasPinnedRows: Flow<Boolean> =
126         topPinnedState.map {
127             when (it) {
128                 is TopPinnedState.Pinned -> it.status.isPinned
129                 is TopPinnedState.NothingPinned -> false
130             }
131         }
132 
133     val isHeadsUpOrAnimatingAway: Flow<Boolean> by lazy {
134         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
135             flowOf(false)
136         } else {
137             combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
138                 hasPinnedRows,
139                 animatingAway ->
140                 hasPinnedRows || animatingAway
141             }
142         }
143     }
144 
145     private val canShowHeadsUp: Flow<Boolean> =
146         combine(
147             faceAuthInteractor.isBypassEnabled,
148             shadeInteractor.isShadeFullyCollapsed,
149             keyguardTransitionInteractor.currentKeyguardState,
150             notificationsKeyguardInteractor.areNotificationsFullyHidden,
151         ) { isBypassEnabled, isShadeCollapsed, keyguardState, areNotificationsHidden ->
152             val isOnLockScreen = keyguardState == KeyguardState.LOCKSCREEN
153             when {
154                 areNotificationsHidden -> false // don't show when notification are hidden
155                 !isShadeCollapsed -> false // don't show when the shade is expanded
156                 isOnLockScreen -> isBypassEnabled // on the lock screen only show for bypass
157                 else -> true // show otherwise
158             }
159         }
160 
161     /**
162      * Emits the pinned notification state as it relates to the status bar. Includes both the pinned
163      * status and key of the notification that's pinned (if there is a pinned notification).
164      */
165     val statusBarHeadsUpState: Flow<TopPinnedState> =
166         combine(topPinnedState, canShowHeadsUp) { topPinnedState, canShowHeadsUp ->
167             if (canShowHeadsUp) {
168                 topPinnedState
169             } else {
170                 TopPinnedState.NothingPinned
171             }
172         }
173 
174     /** Emits the pinned notification status as it relates to the status bar. */
175     val statusBarHeadsUpStatus: Flow<PinnedStatus> =
176         statusBarHeadsUpState.map {
177             when (it) {
178                 is TopPinnedState.Pinned -> it.status
179                 is TopPinnedState.NothingPinned -> PinnedStatus.NotPinned
180             }
181         }
182 
183     fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
184         HeadsUpRowInteractor(key as HeadsUpRowRepository)
185 
186     fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey
187 
188     /** Returns the Notification Key (the standard string) of this row. */
189     fun notificationKey(key: HeadsUpRowKey): String = (key as HeadsUpRowRepository).key
190 
191     fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
192         headsUpRepository.setHeadsUpAnimatingAway(animatingAway)
193     }
194 
195     /** Snooze the currently pinned HUN. */
196     fun snooze() {
197         headsUpRepository.snooze()
198     }
199 
200     /** Unpin all currently pinned HUNs. */
201     fun unpinAll(userUnPinned: Boolean) {
202         headsUpRepository.unpinAll(userUnPinned)
203     }
204 
205     /** Notifies that the current scene transition is idle. */
206     fun onTransitionIdle() {
207         headsUpRepository.releaseAfterExpansion()
208     }
209 }
210 
211 class HeadsUpRowInteractor(repository: HeadsUpRowRepository)
212