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