<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