<lambda>null1 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