<lambda>null1 package com.android.systemui.statusbar.notification.logging
2
3 import android.graphics.Bitmap
4 import android.graphics.drawable.BitmapDrawable
5 import android.graphics.drawable.Drawable
6 import android.util.Log
7 import android.view.View
8 import android.view.ViewGroup
9 import android.widget.ImageView
10 import com.android.internal.R
11 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
12 import com.android.systemui.util.children
13
14 /** Walks view hiearchy of a given notification to estimate its memory use. */
15 object NotificationMemoryViewWalker {
16
17 private const val TAG = "NotificationMemory"
18
19 /** Builder for [NotificationViewUsage] objects. */
20 private class UsageBuilder {
21 private var smallIcon: Int = 0
22 private var largeIcon: Int = 0
23 private var systemIcons: Int = 0
24 private var style: Int = 0
25 private var customViews: Int = 0
26 private var softwareBitmaps = 0
27
28 fun addSmallIcon(smallIconUse: Int) = apply { smallIcon += smallIconUse }
29
30 fun addLargeIcon(largeIconUse: Int) = apply { largeIcon += largeIconUse }
31
32 fun addSystem(systemIconUse: Int) = apply { systemIcons += systemIconUse }
33
34 fun addStyle(styleUse: Int) = apply { style += styleUse }
35
36 fun addSoftwareBitmapPenalty(softwareBitmapUse: Int) = apply {
37 softwareBitmaps += softwareBitmapUse
38 }
39
40 fun addCustomViews(customViewsUse: Int) = apply { customViews += customViewsUse }
41
42 fun build(viewType: ViewType): NotificationViewUsage {
43 return NotificationViewUsage(
44 viewType = viewType,
45 smallIcon = smallIcon,
46 largeIcon = largeIcon,
47 systemIcons = systemIcons,
48 style = style,
49 customViews = customViews,
50 softwareBitmapsPenalty = softwareBitmaps,
51 )
52 }
53 }
54
55 /**
56 * Returns memory usage of public and private views contained in passed
57 * [ExpandableNotificationRow]. Each entry will correspond to one of the [ViewType] values with
58 * [ViewType.TOTAL] totalling all memory use. If a type of view is missing, the corresponding
59 * entry will not appear in resulting list.
60 *
61 * This will return an empty list if the ExpandableNotificationRow has no views inflated.
62 */
63 fun getViewUsage(row: ExpandableNotificationRow?): List<NotificationViewUsage> {
64 if (row == null) {
65 return listOf()
66 }
67
68 // The ordering here is significant since it determines deduplication of seen drawables.
69 val perViewUsages =
70 listOf(
71 getViewUsage(ViewType.PRIVATE_EXPANDED_VIEW, row.privateLayout?.expandedChild),
72 getViewUsage(
73 ViewType.PRIVATE_CONTRACTED_VIEW,
74 row.privateLayout?.contractedChild,
75 ),
76 getViewUsage(ViewType.PRIVATE_HEADS_UP_VIEW, row.privateLayout?.headsUpChild),
77 getViewUsage(
78 ViewType.PUBLIC_VIEW,
79 row.publicLayout?.expandedChild,
80 row.publicLayout?.contractedChild,
81 row.publicLayout?.headsUpChild,
82 ),
83 )
84 .filterNotNull()
85
86 return if (perViewUsages.isNotEmpty()) {
87 // Attach summed totals field only if there was any view actually measured.
88 // This reduces bug report noise and makes checks for collapsed views easier.
89 val totals = getTotalUsage(row)
90 if (totals == null) {
91 perViewUsages
92 } else {
93 perViewUsages + totals
94 }
95 } else {
96 listOf()
97 }
98 }
99
100 /**
101 * Calculate total usage of all views - we need to do a separate traversal to make sure we don't
102 * double count fields.
103 */
104 private fun getTotalUsage(row: ExpandableNotificationRow): NotificationViewUsage? {
105 val seenObjects = hashSetOf<Int>()
106 return getViewUsage(
107 ViewType.TOTAL,
108 row.privateLayout?.expandedChild,
109 row.privateLayout?.contractedChild,
110 row.privateLayout?.headsUpChild,
111 row.publicLayout?.expandedChild,
112 row.publicLayout?.contractedChild,
113 row.publicLayout?.headsUpChild,
114 seenObjects = seenObjects,
115 )
116 }
117
118 private fun getViewUsage(
119 type: ViewType,
120 vararg rootViews: View?,
121 seenObjects: HashSet<Int> = hashSetOf(),
122 ): NotificationViewUsage? {
123 val usageBuilder = lazy { UsageBuilder() }
124 rootViews.forEach { rootView ->
125 (rootView as? ViewGroup)?.let { rootViewGroup ->
126 computeViewHierarchyUse(rootViewGroup, usageBuilder.value, seenObjects)
127 }
128 }
129
130 return if (usageBuilder.isInitialized()) {
131 usageBuilder.value.build(type)
132 } else {
133 null
134 }
135 }
136
137 private fun computeViewHierarchyUse(
138 rootView: ViewGroup,
139 builder: UsageBuilder,
140 seenObjects: HashSet<Int> = hashSetOf(),
141 ) {
142 for (child in rootView.children) {
143 if (child is ViewGroup) {
144 computeViewHierarchyUse(child, builder, seenObjects)
145 } else {
146 computeViewUse(child, builder, seenObjects)
147 }
148 }
149 }
150
151 private fun computeViewUse(view: View, builder: UsageBuilder, seenObjects: HashSet<Int>) {
152 if (view !is ImageView) return
153 val drawable = view.drawable ?: return
154 val drawableRef = System.identityHashCode(drawable)
155 if (seenObjects.contains(drawableRef)) return
156 val drawableUse = computeDrawableUse(drawable, seenObjects)
157 // TODO(b/235451049): We need to make sure we traverse large icon before small icon -
158 // sometimes the large icons are assigned to small icon views and we want to
159 // attribute them to large view in those cases.
160 when (view.id) {
161 R.id.left_icon,
162 R.id.icon,
163 R.id.conversation_icon -> builder.addSmallIcon(drawableUse)
164 R.id.right_icon -> builder.addLargeIcon(drawableUse)
165 R.id.big_picture -> builder.addStyle(drawableUse)
166 // Elements that are part of platform with resources
167 R.id.phishing_alert,
168 R.id.feedback,
169 R.id.alerted_icon,
170 R.id.expand_button_icon,
171 R.id.remote_input_send -> builder.addSystem(drawableUse)
172 // Custom view ImageViews
173 else -> {
174 if (Log.isLoggable(TAG, Log.DEBUG)) {
175 Log.d(TAG, "Custom view: ${identifierForView(view)}")
176 }
177 builder.addCustomViews(drawableUse)
178 }
179 }
180
181 if (isDrawableSoftwareBitmap(drawable)) {
182 builder.addSoftwareBitmapPenalty(drawableUse)
183 }
184
185 seenObjects.add(drawableRef)
186 }
187
188 private fun computeDrawableUse(drawable: Drawable, seenObjects: HashSet<Int>): Int =
189 when (drawable) {
190 is BitmapDrawable -> {
191 drawable.bitmap?.let {
192 val ref = System.identityHashCode(it)
193 if (seenObjects.contains(ref)) {
194 0
195 } else {
196 seenObjects.add(ref)
197 it.allocationByteCount
198 }
199 } ?: 0
200 }
201 else -> 0
202 }
203
204 private fun isDrawableSoftwareBitmap(drawable: Drawable) =
205 drawable is BitmapDrawable && drawable.bitmap?.config != Bitmap.Config.HARDWARE
206
207 private fun identifierForView(view: View) =
208 if (view.id == View.NO_ID) {
209 "no-id"
210 } else {
211 view.resources.getResourceName(view.id)
212 }
213 }
214