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