• 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"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  *
14  */
15 
16 package com.android.systemui.statusbar.notification.domain.interactor
17 
18 import com.android.systemui.dagger.SysUISingleton
19 import com.android.systemui.dagger.qualifiers.Background
20 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
21 import com.android.systemui.statusbar.notification.data.model.NotifStats
22 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
23 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
24 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
25 import com.android.systemui.statusbar.notification.shared.CallType
26 import javax.inject.Inject
27 import kotlinx.coroutines.CoroutineDispatcher
28 import kotlinx.coroutines.flow.Flow
29 import kotlinx.coroutines.flow.distinctUntilChanged
30 import kotlinx.coroutines.flow.flowOf
31 import kotlinx.coroutines.flow.flowOn
32 import kotlinx.coroutines.flow.map
33 
34 @SysUISingleton
35 class ActiveNotificationsInteractor
36 @Inject
37 constructor(
38     private val repository: ActiveNotificationListRepository,
39     @Background private val backgroundDispatcher: CoroutineDispatcher,
40 ) {
41     /**
42      * Top level list of Notifications actively presented to the user in the notification stack, in
43      * order.
44      */
45     val topLevelRepresentativeNotifications: Flow<List<ActiveNotificationModel>> =
46         repository.activeNotifications
47             .map { store ->
48                 store.renderList.map { key ->
49                     val entry =
50                         store[key]
51                             ?: error(
52                                 "Could not find notification with key $key in active notif store."
53                             )
54                     when (entry) {
55                         is ActiveNotificationGroupModel -> entry.summary
56                         is ActiveNotificationModel -> entry
57                     }
58                 }
59             }
60             .flowOn(backgroundDispatcher)
61 
62     /**
63      * Flattened list of Notifications actively presented to the user in the notification stack, in
64      * order.
65      */
66     val allRepresentativeNotifications: Flow<Map<String, ActiveNotificationModel>> =
67         repository.activeNotifications.map { store -> store.individuals }
68 
69     /** Size of the flattened list of Notifications actively presented in the stack. */
70     val allNotificationsCount: Flow<Int> =
71         repository.activeNotifications.map { store -> store.individuals.size }
72 
73     /**
74      * The same as [allNotificationsCount], but without flows, for easy access in synchronous code.
75      */
76     val allNotificationsCountValue: Int
77         get() = repository.activeNotifications.value.individuals.size
78 
79     /**
80      * The notifications that are promoted and ongoing.
81      *
82      * This *may* include ongoing call notifications if the call notification also meets promotion
83      * criteria.
84      */
85     val promotedOngoingNotifications: Flow<List<ActiveNotificationModel>> =
86         if (StatusBarNotifChips.isEnabled) {
87             topLevelRepresentativeNotifications
88                 .map { notifs -> notifs.filter { it.promotedContent != null } }
89                 .distinctUntilChanged()
90                 .flowOn(backgroundDispatcher)
91         } else {
92             flowOf(emptyList())
93         }
94 
95     /**
96      * The priority ongoing call notification, or null if there is no ongoing call.
97      *
98      * The output model is guaranteed to have [ActiveNotificationModel.callType] to be equal to
99      * [CallType.Ongoing].
100      */
101     val ongoingCallNotification: Flow<ActiveNotificationModel?> =
102         allRepresentativeNotifications
103             .map { notifMap ->
104                 notifMap.values
105                     .filter { it.isOngoingCallNotification() }
106                     // Once a call has started, its `whenTime` should stay the same, so we can use
107                     // it as a stable sort value.
108                     .minByOrNull { it.whenTime }
109             }
110             .distinctUntilChanged()
111             .flowOn(backgroundDispatcher)
112 
113     /** Are any notifications being actively presented in the notification stack? */
114     val areAnyNotificationsPresent: Flow<Boolean> =
115         repository.activeNotifications
116             .map { it.renderList.isNotEmpty() }
117             .distinctUntilChanged()
118             .flowOn(backgroundDispatcher)
119 
120     /**
121      * The same as [areAnyNotificationsPresent], but without flows, for easy access in synchronous
122      * code.
123      */
124     val areAnyNotificationsPresentValue: Boolean
125         get() = repository.activeNotifications.value.renderList.isNotEmpty()
126 
127     /**
128      * Map of notification key to rank, where rank is the 0-based index of the notification in the
129      * system server, meaning that in the unfiltered flattened list of notification entries. Used
130      * for logging purposes.
131      *
132      * @see [com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger].
133      */
134     val activeNotificationRanks: Flow<Map<String, Int>> =
135         repository.activeNotifications.map { store -> store.rankingsMap }
136 
137     /** Are there are any notifications that can be cleared by the "Clear all" button? */
138     val hasClearableNotifications: Flow<Boolean> =
139         repository.notifStats
140             .map { it.hasClearableAlertingNotifs || it.hasClearableSilentNotifs }
141             .distinctUntilChanged()
142             .flowOn(backgroundDispatcher)
143 
144     val hasClearableAlertingNotifications: Flow<Boolean> =
145         repository.notifStats
146             .map { it.hasClearableAlertingNotifs }
147             .distinctUntilChanged()
148             .flowOn(backgroundDispatcher)
149 
150     val hasNonClearableSilentNotifications: Flow<Boolean> =
151         repository.notifStats
152             .map { it.hasNonClearableSilentNotifs }
153             .distinctUntilChanged()
154             .flowOn(backgroundDispatcher)
155 
156     fun setNotifStats(notifStats: NotifStats) {
157         repository.notifStats.value = notifStats
158     }
159 
160     companion object {
161         fun ActiveNotificationModel.isOngoingCallNotification() = this.callType == CallType.Ongoing
162     }
163 }
164