• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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