1 package com.android.systemui.statusbar.notification.logging 2 3 import android.app.Notification 4 import android.app.Notification.BigPictureStyle 5 import android.app.Notification.BigTextStyle 6 import android.app.Notification.CallStyle 7 import android.app.Notification.DecoratedCustomViewStyle 8 import android.app.Notification.InboxStyle 9 import android.app.Notification.MediaStyle 10 import android.app.Notification.MessagingStyle 11 import android.app.Person 12 import android.graphics.Bitmap 13 import android.graphics.drawable.Icon 14 import android.os.Bundle 15 import android.os.Parcel 16 import android.os.Parcelable 17 import android.stats.sysui.NotificationEnums 18 import androidx.annotation.WorkerThread 19 import com.android.systemui.statusbar.notification.NotificationUtils 20 import com.android.systemui.statusbar.notification.collection.NotificationEntry 21 22 /** Calculates estimated memory usage of [Notification] and [NotificationEntry] objects. */ 23 internal object NotificationMemoryMeter { 24 25 private const val CAR_EXTENSIONS = "android.car.EXTENSIONS" 26 private const val CAR_EXTENSIONS_LARGE_ICON = "large_icon" 27 private const val TV_EXTENSIONS = "android.tv.EXTENSIONS" 28 private const val WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS" 29 private const val WEARABLE_EXTENSIONS_BACKGROUND = "background" 30 private const val AUTOGROUP_KEY = "ranker_group" 31 32 /** Returns a list of memory use entries for currently shown notifications. */ 33 @WorkerThread 34 fun notificationMemoryUse( 35 notifications: Collection<NotificationEntry>, 36 ): List<NotificationMemoryUsage> { 37 return notifications 38 .asSequence() 39 .map { entry -> 40 val packageName = entry.sbn.packageName 41 val uid = entry.sbn.uid 42 val notificationObjectUsage = 43 notificationMemoryUse(entry.sbn.notification, hashSetOf()) 44 val notificationViewUsage = NotificationMemoryViewWalker.getViewUsage(entry.row) 45 NotificationMemoryUsage( 46 packageName, 47 uid, 48 NotificationUtils.logKey(entry.sbn.key), 49 entry.sbn.notification, 50 notificationObjectUsage, 51 notificationViewUsage 52 ) 53 } 54 .toList() 55 } 56 57 @WorkerThread 58 fun notificationMemoryUse( 59 entry: NotificationEntry, 60 seenBitmaps: HashSet<Int> = hashSetOf(), 61 ): NotificationMemoryUsage { 62 return NotificationMemoryUsage( 63 entry.sbn.packageName, 64 entry.sbn.uid, 65 NotificationUtils.logKey(entry.sbn.key), 66 entry.sbn.notification, 67 notificationMemoryUse(entry.sbn.notification, seenBitmaps), 68 NotificationMemoryViewWalker.getViewUsage(entry.row) 69 ) 70 } 71 72 /** 73 * Computes the estimated memory usage of a given [Notification] object. It'll attempt to 74 * inspect Bitmaps in the object and provide summary of memory usage. 75 */ 76 @WorkerThread 77 fun notificationMemoryUse( 78 notification: Notification, 79 seenBitmaps: HashSet<Int> = hashSetOf(), 80 ): NotificationObjectUsage { 81 val extras = notification.extras 82 val smallIconUse = computeIconUse(notification.smallIcon, seenBitmaps) 83 val largeIconUse = computeIconUse(notification.getLargeIcon(), seenBitmaps) 84 85 // Collect memory usage of extra styles 86 87 // Big Picture 88 val bigPictureIconUse = 89 computeParcelableUse(extras, Notification.EXTRA_LARGE_ICON_BIG, seenBitmaps) 90 val bigPictureUse = 91 computeParcelableUse(extras, Notification.EXTRA_PICTURE, seenBitmaps) + 92 computeParcelableUse(extras, Notification.EXTRA_PICTURE_ICON, seenBitmaps) 93 94 // People 95 val peopleList = extras.getParcelableArrayList<Person>(Notification.EXTRA_PEOPLE_LIST) 96 val peopleUse = 97 peopleList?.sumOf { person -> computeIconUse(person.icon, seenBitmaps) } ?: 0 98 99 // Calling 100 val callingPersonUse = 101 computeParcelableUse(extras, Notification.EXTRA_CALL_PERSON, seenBitmaps) 102 val verificationIconUse = 103 computeParcelableUse(extras, Notification.EXTRA_VERIFICATION_ICON, seenBitmaps) 104 105 // Messages 106 val messages = 107 Notification.MessagingStyle.Message.getMessagesFromBundleArray( 108 extras.getParcelableArray(Notification.EXTRA_MESSAGES) 109 ) 110 val messagesUse = 111 messages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) } 112 val historicMessages = 113 Notification.MessagingStyle.Message.getMessagesFromBundleArray( 114 extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES) 115 ) 116 val historyicMessagesUse = 117 historicMessages.sumOf { msg -> computeIconUse(msg.senderPerson?.icon, seenBitmaps) } 118 119 // Extenders 120 val carExtender = extras.getBundle(CAR_EXTENSIONS) 121 val carExtenderSize = carExtender?.let { computeBundleSize(it) } ?: 0 122 val carExtenderIcon = 123 computeParcelableUse(carExtender, CAR_EXTENSIONS_LARGE_ICON, seenBitmaps) 124 125 val tvExtender = extras.getBundle(TV_EXTENSIONS) 126 val tvExtenderSize = tvExtender?.let { computeBundleSize(it) } ?: 0 127 128 val wearExtender = extras.getBundle(WEARABLE_EXTENSIONS) 129 val wearExtenderSize = wearExtender?.let { computeBundleSize(it) } ?: 0 130 val wearExtenderBackground = 131 computeParcelableUse(wearExtender, WEARABLE_EXTENSIONS_BACKGROUND, seenBitmaps) 132 133 val style = 134 if (notification.group == AUTOGROUP_KEY) { 135 NotificationEnums.STYLE_RANKER_GROUP 136 } else { 137 styleEnum(notification.notificationStyle) 138 } 139 140 val hasCustomView = notification.contentView != null || notification.bigContentView != null 141 val extrasSize = computeBundleSize(extras) 142 143 return NotificationObjectUsage( 144 smallIcon = smallIconUse, 145 largeIcon = largeIconUse, 146 extras = extrasSize, 147 style = style, 148 styleIcon = 149 bigPictureIconUse + 150 peopleUse + 151 callingPersonUse + 152 verificationIconUse + 153 messagesUse + 154 historyicMessagesUse, 155 bigPicture = bigPictureUse, 156 extender = 157 carExtenderSize + 158 carExtenderIcon + 159 tvExtenderSize + 160 wearExtenderSize + 161 wearExtenderBackground, 162 hasCustomView = hasCustomView 163 ) 164 } 165 166 /** 167 * Returns logging style enum based on current style class. 168 * 169 * @return style value in [NotificationEnums] 170 */ 171 private fun styleEnum(style: Class<out Notification.Style>?): Int = 172 when (style?.name) { 173 null -> NotificationEnums.STYLE_NONE 174 BigTextStyle::class.java.name -> NotificationEnums.STYLE_BIG_TEXT 175 BigPictureStyle::class.java.name -> NotificationEnums.STYLE_BIG_PICTURE 176 InboxStyle::class.java.name -> NotificationEnums.STYLE_INBOX 177 MediaStyle::class.java.name -> NotificationEnums.STYLE_MEDIA 178 DecoratedCustomViewStyle::class.java.name -> 179 NotificationEnums.STYLE_DECORATED_CUSTOM_VIEW 180 MessagingStyle::class.java.name -> NotificationEnums.STYLE_MESSAGING 181 CallStyle::class.java.name -> NotificationEnums.STYLE_CALL 182 else -> NotificationEnums.STYLE_UNSPECIFIED 183 } 184 185 /** 186 * Calculates size of the bundle data (excluding FDs and other shared objects like ashmem 187 * bitmaps). Can be slow. 188 */ 189 private fun computeBundleSize(extras: Bundle): Int { 190 val parcel = Parcel.obtain() 191 try { 192 extras.writeToParcel(parcel, 0) 193 return parcel.dataSize() 194 } finally { 195 parcel.recycle() 196 } 197 } 198 199 /** 200 * Deserializes [Icon], [Bitmap] or [Person] from extras and computes its memory use. Returns 0 201 * if the key does not exist in extras. 202 */ 203 private fun computeParcelableUse(extras: Bundle?, key: String, seenBitmaps: HashSet<Int>): Int { 204 return when (val parcelable = extras?.getParcelable<Parcelable>(key)) { 205 is Bitmap -> computeBitmapUse(parcelable, seenBitmaps) 206 is Icon -> computeIconUse(parcelable, seenBitmaps) 207 is Person -> computeIconUse(parcelable.icon, seenBitmaps) 208 else -> 0 209 } 210 } 211 212 /** 213 * Calculates the byte size of bitmaps or data in the Icon object. Returns 0 if the icon is 214 * defined via Uri or a resource. 215 * 216 * @return memory usage in bytes or 0 if the icon is Uri/Resource based 217 */ 218 private fun computeIconUse(icon: Icon?, seenBitmaps: HashSet<Int>): Int = 219 when (icon?.type) { 220 Icon.TYPE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps) 221 Icon.TYPE_ADAPTIVE_BITMAP -> computeBitmapUse(icon.bitmap, seenBitmaps) 222 Icon.TYPE_DATA -> computeDataUse(icon, seenBitmaps) 223 else -> 0 224 } 225 226 /** 227 * Returns the amount of memory a given bitmap is using. If the bitmap reference is part of 228 * seenBitmaps set, this method returns 0 to avoid double counting. 229 * 230 * @return memory usage of the bitmap in bytes 231 */ 232 private fun computeBitmapUse(bitmap: Bitmap, seenBitmaps: HashSet<Int>? = null): Int { 233 val refId = System.identityHashCode(bitmap) 234 if (seenBitmaps?.contains(refId) == true) { 235 return 0 236 } 237 238 seenBitmaps?.add(refId) 239 return bitmap.allocationByteCount 240 } 241 242 private fun computeDataUse(icon: Icon, seenBitmaps: HashSet<Int>): Int { 243 val refId = System.identityHashCode(icon.dataBytes) 244 if (seenBitmaps.contains(refId)) { 245 return 0 246 } 247 248 seenBitmaps.add(refId) 249 return icon.dataLength 250 } 251 } 252