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.chips.notification.domain.interactor 18 19 import com.android.systemui.activity.data.model.AppVisibilityModel 20 import com.android.systemui.activity.data.repository.ActivityManagerRepository 21 import com.android.systemui.log.LogBuffer 22 import com.android.systemui.log.core.Logger 23 import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad 24 import com.android.systemui.statusbar.chips.StatusBarChipsLog 25 import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel 26 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays 27 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel 28 import dagger.assisted.Assisted 29 import dagger.assisted.AssistedFactory 30 import dagger.assisted.AssistedInject 31 import kotlinx.coroutines.flow.Flow 32 import kotlinx.coroutines.flow.MutableStateFlow 33 import kotlinx.coroutines.flow.combine 34 35 /** 36 * Interactor representing a single notification's status bar chip. 37 * 38 * [startingModel.key] dictates which notification this interactor corresponds to - all updates sent 39 * to this interactor via [setNotification] should only be for the notification with the same key. 40 * 41 * [StatusBarNotificationChipsInteractor] will collect all the individual instances of this 42 * interactor and send all the necessary information to the UI layer. 43 * 44 * @property creationTime the time when the notification first appeared as promoted. 45 */ 46 class SingleNotificationChipInteractor 47 @AssistedInject 48 constructor( 49 @Assisted startingModel: ActiveNotificationModel, 50 @Assisted val creationTime: Long, 51 private val activityManagerRepository: ActivityManagerRepository, 52 @StatusBarChipsLog private val logBuffer: LogBuffer, 53 ) { 54 private val key = startingModel.key 55 private val uid = startingModel.uid 56 private val logger = Logger(logBuffer, "Notif".pad()) 57 // [StatusBarChipLogTag] recommends a max tag length of 20, so [extraLogTag] should NOT be the 58 // top-level tag. It should instead be provided as the first string in each log message. 59 private val extraLogTag = "SingleNotifChipInteractor[key=$key][id=${hashCode()}]" 60 61 init { 62 if (startingModel.promotedContent == null) { 63 logger.e({ "$str1: Starting model has promotedContent=null, which shouldn't happen" }) { 64 str1 = extraLogTag 65 } 66 } 67 } 68 69 private val _notificationModel = MutableStateFlow(startingModel) 70 71 /** 72 * Sets the new notification info corresponding to this interactor. The key on [model] *must* 73 * match the key on the original [startingModel], otherwise the update won't be processed. 74 */ 75 fun setNotification(model: ActiveNotificationModel) { 76 if (model.key != this.key) { 77 logger.w({ "$str1: received model for different key $str2" }) { 78 str1 = extraLogTag 79 str2 = model.key 80 } 81 return 82 } 83 if (model.promotedContent == null) { 84 logger.e({ 85 "$str1: received model with promotedContent=null, which shouldn't happen" 86 }) { 87 str1 = extraLogTag 88 } 89 return 90 } 91 92 if (model.uid != uid) { 93 logger.e({ 94 "$str1: received model with different uid, which shouldn't happen. " + 95 "Original UID: $int1, New UID: $int2. " + 96 "Proceeding as usual, but app visibility changes will be for *old* UID." 97 }) { 98 str1 = extraLogTag 99 int1 = uid 100 int2 = model.uid 101 } 102 } 103 _notificationModel.value = model 104 } 105 106 /** Details about when the app managing the notification was & is visible to the user. */ 107 private val appVisibility: Flow<AppVisibilityModel> = 108 activityManagerRepository.createAppVisibilityFlow(uid, logger, extraLogTag) 109 110 /** 111 * Emits this notification's status bar chip, or null if this notification shouldn't show a 112 * status bar chip. 113 */ 114 val notificationChip: Flow<NotificationChipModel?> = 115 combine(_notificationModel, appVisibility) { notif, appVisibility -> 116 notif.toNotificationChipModel(appVisibility) 117 } 118 119 private fun ActiveNotificationModel.toNotificationChipModel( 120 appVisibility: AppVisibilityModel 121 ): NotificationChipModel? { 122 val promotedContent = this.promotedContent 123 if (promotedContent == null) { 124 logger.w({ 125 "$str1: Can't show chip because promotedContent=null, which shouldn't happen" 126 }) { 127 str1 = extraLogTag 128 } 129 return null 130 } 131 val statusBarChipIconView = this.statusBarChipIconView 132 if (statusBarChipIconView == null) { 133 if (!StatusBarConnectedDisplays.isEnabled) { 134 logger.w({ "$str1: Can't show chip because status bar chip icon view is null" }) { 135 str1 = extraLogTag 136 } 137 // When the flag is disabled, we keep the old behavior of returning null. 138 // When the flag is enabled, the icon will always be null, and will later be 139 // fetched in the UI layer using the notification key. 140 return null 141 } 142 } 143 144 return NotificationChipModel( 145 key = key, 146 appName = appName, 147 statusBarChipIconView = statusBarChipIconView, 148 promotedContent = promotedContent, 149 creationTime = creationTime, 150 isAppVisible = appVisibility.isAppCurrentlyVisible, 151 lastAppVisibleTime = appVisibility.lastAppVisibleTime, 152 instanceId = instanceId, 153 ) 154 } 155 156 @AssistedFactory 157 fun interface Factory { 158 fun create( 159 startingModel: ActiveNotificationModel, 160 creationTime: Long, 161 ): SingleNotificationChipInteractor 162 } 163 } 164