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