• 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.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