• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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.promoted.shared.model
18 
19 import android.annotation.CurrentTimeMillisLong
20 import android.annotation.DrawableRes
21 import android.annotation.ElapsedRealtimeLong
22 import android.app.Notification
23 import android.app.Notification.FLAG_PROMOTED_ONGOING
24 import androidx.annotation.ColorInt
25 import com.android.internal.widget.NotificationProgressModel
26 import com.android.systemui.Flags
27 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
28 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
29 import com.android.systemui.statusbar.notification.row.ImageResult
30 import com.android.systemui.statusbar.notification.row.LazyImage
31 import com.android.systemui.statusbar.notification.row.shared.ImageModel
32 import com.android.systemui.util.Compile
33 
34 data class PromotedNotificationContentModels(
35     /** The potentially redacted version of the content that will be exposed to the public */
36     val publicVersion: PromotedNotificationContentModel,
37     /** The unredacted version of the content that will be kept private */
38     val privateVersion: PromotedNotificationContentModel,
39 ) {
40     val key: String
41         get() = privateVersion.identity.key
42 
43     init {
<lambda>null44         check(publicVersion.identity.key == privateVersion.identity.key) {
45             "public and private models must have the same key"
46         }
47     }
48 
toRedactedStringnull49     fun toRedactedString(): String {
50         val publicVersionString =
51             "==privateVersion".takeIf { privateVersion === publicVersion }
52                 ?: publicVersion.toRedactedString()
53         return ("PromotedNotificationContentModels(" +
54             "privateVersion=${privateVersion.toRedactedString()}, " +
55             "publicVersion=$publicVersionString)")
56     }
57 }
58 
59 /**
60  * The content needed to render a promoted notification to surfaces besides the notification stack,
61  * like the skeleton view on AOD or the status bar chip.
62  */
63 data class PromotedNotificationContentModel(
64     val identity: Identity,
65 
66     // for all styles:
67     /**
68      * True if this notification was automatically promoted - see [AutomaticPromotionCoordinator].
69      */
70     val wasPromotedAutomatically: Boolean,
71     val smallIcon: ImageModel?,
72     val iconLevel: Int,
73     val appName: CharSequence?,
74     val subText: CharSequence?,
75     val shortCriticalText: String?,
76     /**
77      * The timestamp associated with the notification. Null if the timestamp should not be
78      * displayed.
79      */
80     val time: When?,
81     val lastAudiblyAlertedMs: Long,
82     @DrawableRes val profileBadgeResId: Int?,
83     val title: CharSequence?,
84     val text: CharSequence?,
85     val skeletonLargeIcon: ImageModel?,
86     val oldProgress: OldProgress?,
87     val colors: Colors,
88     val style: Style,
89 
90     // for CallStyle:
91     val verificationIcon: ImageModel?,
92     val verificationText: CharSequence?,
93 
94     // for ProgressStyle:
95     val newProgress: NotificationProgressModel?,
96 ) {
97     class Builder(val key: String) {
98         var wasPromotedAutomatically: Boolean = false
99         var smallIcon: ImageModel? = null
100         var iconLevel: Int = 0
101         var appName: CharSequence? = null
102         var subText: CharSequence? = null
103         var time: When? = null
104         var shortCriticalText: String? = null
105         var lastAudiblyAlertedMs: Long = 0L
106         @DrawableRes var profileBadgeResId: Int? = null
107         var title: CharSequence? = null
108         var text: CharSequence? = null
109         var skeletonLargeIcon: ImageModel? = null
110         var oldProgress: OldProgress? = null
111         var style: Style = Style.Ineligible
112         var colors: Colors = Colors(backgroundColor = 0, primaryTextColor = 0)
113 
114         // for CallStyle:
115         var verificationIcon: ImageModel? = null
116         var verificationText: CharSequence? = null
117 
118         // for ProgressStyle:
119         var newProgress: NotificationProgressModel? = null
120 
buildnull121         fun build() =
122             PromotedNotificationContentModel(
123                 identity = Identity(key, style),
124                 wasPromotedAutomatically = wasPromotedAutomatically,
125                 smallIcon = smallIcon,
126                 iconLevel = iconLevel,
127                 appName = appName,
128                 subText = subText,
129                 shortCriticalText = shortCriticalText,
130                 time = time,
131                 lastAudiblyAlertedMs = lastAudiblyAlertedMs,
132                 profileBadgeResId = profileBadgeResId,
133                 title = title,
134                 text = text,
135                 skeletonLargeIcon = skeletonLargeIcon,
136                 oldProgress = oldProgress,
137                 colors = colors,
138                 style = style,
139                 verificationIcon = verificationIcon,
140                 verificationText = verificationText,
141                 newProgress = newProgress,
142             )
143     }
144 
145     data class Identity(val key: String, val style: Style)
146 
147     /** The timestamp associated with a notification, along with the mode used to display it. */
148     sealed class When {
149         /** Show the notification's time as a timestamp. */
150         data class Time(@CurrentTimeMillisLong val currentTimeMillis: Long) : When()
151 
152         /**
153          * Show the notification's time as a chronometer that counts up or down (based on
154          * [isCountDown]) to [elapsedRealtimeMillis].
155          */
156         data class Chronometer(
157             @ElapsedRealtimeLong val elapsedRealtimeMillis: Long,
158             val isCountDown: Boolean,
159         ) : When()
160     }
161 
162     /** The colors used to display the notification. */
163     data class Colors(@ColorInt val backgroundColor: Int, @ColorInt val primaryTextColor: Int)
164 
165     /** The fields needed to render the old-style progress bar. */
166     data class OldProgress(val progress: Int, val max: Int, val isIndeterminate: Boolean)
167 
168     /** The promotion-eligible style of a notification, or [Style.Ineligible] if not. */
169     enum class Style {
170         Base, // style == null
171         CollapsedBase, // style == null
172         BigPicture,
173         BigText,
174         Call,
175         CollapsedCall,
176         Progress,
177         Ineligible,
178     }
179 
toRedactedStringnull180     fun toRedactedString(): String {
181         return ("PromotedNotificationContentModel(" +
182             "identity=$identity, " +
183             "wasPromotedAutomatically=$wasPromotedAutomatically, " +
184             "smallIcon=${smallIcon?.toRedactedString()}, " +
185             "appName=$appName, " +
186             "subText=${subText?.toRedactedString()}, " +
187             "shortCriticalText=$shortCriticalText, " +
188             "time=$time, " +
189             "lastAudiblyAlertedMs=$lastAudiblyAlertedMs, " +
190             "profileBadgeResId=$profileBadgeResId, " +
191             "title=${title?.toRedactedString()}, " +
192             "text=${text?.toRedactedString()}, " +
193             "skeletonLargeIcon=${skeletonLargeIcon?.toRedactedString()}, " +
194             "oldProgress=$oldProgress, " +
195             "colors=$colors, " +
196             "style=$style, " +
197             "verificationIcon=$verificationIcon, " +
198             "verificationText=$verificationText, " +
199             "newProgress=$newProgress)")
200     }
201 
toRedactedStringnull202     private fun CharSequence.toRedactedString(): String = "[$length]"
203 
204     private fun ImageModel.toRedactedString(): String {
205         return when (this) {
206             is LazyImage -> this.toRedactedString()
207             else -> this.toString()
208         }
209     }
210 
toRedactedStringnull211     private fun LazyImage.toRedactedString(): String {
212         return ("LazyImage(" +
213             "icon=[${icon.javaClass.simpleName}], " +
214             "sizeClass=$sizeClass, " +
215             "transform=$transform, " +
216             "result=${result?.toRedactedString()})")
217     }
218 
toRedactedStringnull219     private fun ImageResult.toRedactedString(): String {
220         return when (this) {
221             is ImageResult.Empty -> this.toString()
222             is ImageResult.Image -> "Image(drawable=[${drawable.javaClass.simpleName}])"
223         }
224     }
225 
226     companion object {
227         @JvmStatic
featureFlagEnablednull228         fun featureFlagEnabled(): Boolean =
229             PromotedNotificationUi.isEnabled || StatusBarNotifChips.isEnabled
230 
231         /**
232          * Returns true if the given notification should be considered promoted when deciding
233          * whether or not to show the status bar chip UI.
234          */
235         @JvmStatic
236         fun isPromotedForStatusBarChip(notification: Notification): Boolean {
237             if (Compile.IS_DEBUG && Flags.debugLiveUpdatesPromoteAll()) {
238                 return true
239             }
240 
241             // Notification.isPromotedOngoing checks the ui_rich_ongoing flag, but we want the
242             // status bar chip to be ready before all the features behind the ui_rich_ongoing flag
243             // are ready.
244             val isPromotedForStatusBarChip =
245                 StatusBarNotifChips.isEnabled && (notification.flags and FLAG_PROMOTED_ONGOING) != 0
246             return notification.isPromotedOngoing() || isPromotedForStatusBarChip
247         }
248     }
249 }
250