1 /*
2  *
<lambda>null3  * Copyright (C) 2022 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.systemui.statusbar.notification.logging
19 
20 import android.stats.sysui.NotificationEnums
21 import android.util.Log
22 import com.android.systemui.Dumpable
23 import com.android.systemui.dagger.SysUISingleton
24 import com.android.systemui.dump.DumpManager
25 import com.android.systemui.dump.DumpsysTableLogger
26 import com.android.systemui.dump.Row
27 import com.android.systemui.statusbar.notification.collection.NotifPipeline
28 import dalvik.annotation.optimization.NeverCompile
29 import java.io.PrintWriter
30 import javax.inject.Inject
31 
32 /** Dumps current notification memory use to bug reports for easier debugging. */
33 @SysUISingleton
34 class NotificationMemoryDumper
35 @Inject
36 constructor(val dumpManager: DumpManager, val notificationPipeline: NotifPipeline) : Dumpable {
37 
38     fun init() {
39         dumpManager.registerNormalDumpable(javaClass.simpleName, this)
40         Log.i("NotificationMemory", "Registered dumpable.")
41     }
42 
43     @NeverCompile
44     override fun dump(pw: PrintWriter, args: Array<out String>) {
45         val memoryUse =
46             NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs)
47                 .sortedWith(compareBy({ it.packageName }, { it.notificationKey }))
48         dumpNotificationObjects(pw, memoryUse)
49         dumpNotificationViewUsage(pw, memoryUse)
50     }
51 
52     /** Renders a table of notification object usage into passed [PrintWriter]. */
53     private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) {
54         val columns =
55             listOf(
56                 "Package",
57                 "Small Icon",
58                 "Large Icon",
59                 "Style",
60                 "Style Icon",
61                 "Big Picture",
62                 "Extender",
63                 "Extras",
64                 "Custom View",
65                 "Key"
66             )
67         val rows: List<Row> =
68             memoryUse.map {
69                 listOf(
70                     it.packageName,
71                     toKb(it.objectUsage.smallIcon),
72                     toKb(it.objectUsage.largeIcon),
73                     styleEnumToString(it.objectUsage.style),
74                     toKb(it.objectUsage.styleIcon),
75                     toKb(it.objectUsage.bigPicture),
76                     toKb(it.objectUsage.extender),
77                     toKb(it.objectUsage.extras),
78                     it.objectUsage.hasCustomView.toString(),
79                     // | is a  field delimiter in the output format so we need to replace
80                     // it to avoid breakage.
81                     it.notificationKey.replace('|', '│')
82                 )
83             }
84 
85         // Calculate totals for easily glanceable summary.
86         data class Totals(
87             var smallIcon: Int = 0,
88             var largeIcon: Int = 0,
89             var styleIcon: Int = 0,
90             var bigPicture: Int = 0,
91             var extender: Int = 0,
92             var extras: Int = 0,
93         )
94 
95         val totals =
96             memoryUse.fold(Totals()) { t, usage ->
97                 t.smallIcon += usage.objectUsage.smallIcon
98                 t.largeIcon += usage.objectUsage.largeIcon
99                 t.styleIcon += usage.objectUsage.styleIcon
100                 t.bigPicture += usage.objectUsage.bigPicture
101                 t.extender += usage.objectUsage.extender
102                 t.extras += usage.objectUsage.extras
103                 t
104             }
105 
106         val totalsRow: List<Row> =
107             listOf(
108                 listOf(
109                     "TOTALS",
110                     toKb(totals.smallIcon),
111                     toKb(totals.largeIcon),
112                     "",
113                     toKb(totals.styleIcon),
114                     toKb(totals.bigPicture),
115                     toKb(totals.extender),
116                     toKb(totals.extras),
117                     "",
118                     ""
119                 )
120             )
121         val tableLogger = DumpsysTableLogger("Notification Object Usage", columns, rows + totalsRow)
122         tableLogger.printTableData(pw)
123     }
124 
125     /** Renders a table of notification view usage into passed [PrintWriter] */
126     private fun dumpNotificationViewUsage(
127         pw: PrintWriter,
128         memoryUse: List<NotificationMemoryUsage>,
129     ) {
130 
131         data class Totals(
132             var smallIcon: Int = 0,
133             var largeIcon: Int = 0,
134             var style: Int = 0,
135             var customViews: Int = 0,
136             var softwareBitmapsPenalty: Int = 0,
137         )
138 
139         val columns =
140             listOf(
141                 "Package",
142                 "View Type",
143                 "Small Icon",
144                 "Large Icon",
145                 "Style Use",
146                 "Custom View",
147                 "Software Bitmaps",
148                 "Key"
149             )
150         val rows =
151             memoryUse
152                 .filter { it.viewUsage.isNotEmpty() }
153                 .flatMap { use ->
154                     use.viewUsage.map { view ->
155                         listOf(
156                             use.packageName,
157                             view.viewType.toString(),
158                             toKb(view.smallIcon),
159                             toKb(view.largeIcon),
160                             toKb(view.style),
161                             toKb(view.customViews),
162                             toKb(view.softwareBitmapsPenalty),
163                             // | is a  field delimiter in the output format so we need to replace
164                             // it to avoid breakage.
165                             use.notificationKey.replace('|', '│')
166                         )
167                     }
168                 }
169 
170         val totals = Totals()
171         memoryUse
172             .filter { it.viewUsage.isNotEmpty() }
173             .map { it.viewUsage.firstOrNull { view -> view.viewType == ViewType.TOTAL } }
174             .filterNotNull()
175             .forEach { view ->
176                 totals.smallIcon += view.smallIcon
177                 totals.largeIcon += view.largeIcon
178                 totals.style += view.style
179                 totals.customViews += view.customViews
180                 totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty
181             }
182 
183         val totalsRow: List<Row> =
184             listOf(
185                 listOf(
186                     "TOTALS",
187                     "",
188                     toKb(totals.smallIcon),
189                     toKb(totals.largeIcon),
190                     toKb(totals.style),
191                     toKb(totals.customViews),
192                     toKb(totals.softwareBitmapsPenalty),
193                     ""
194                 )
195             )
196         val tableLogger = DumpsysTableLogger("Notification View Usage", columns, rows + totalsRow)
197         tableLogger.printTableData(pw)
198     }
199 
200     private fun styleEnumToString(styleEnum: Int): String =
201         when (styleEnum) {
202             NotificationEnums.STYLE_UNSPECIFIED -> "Unspecified"
203             NotificationEnums.STYLE_NONE -> "None"
204             NotificationEnums.STYLE_BIG_PICTURE -> "BigPicture"
205             NotificationEnums.STYLE_BIG_TEXT -> "BigText"
206             NotificationEnums.STYLE_CALL -> "Call"
207             NotificationEnums.STYLE_DECORATED_CUSTOM_VIEW -> "DCustomView"
208             NotificationEnums.STYLE_INBOX -> "Inbox"
209             NotificationEnums.STYLE_MEDIA -> "Media"
210             NotificationEnums.STYLE_MESSAGING -> "Messaging"
211             NotificationEnums.STYLE_RANKER_GROUP -> "RankerGroup"
212             else -> "Unknown"
213         }
214 
215     private fun toKb(bytes: Int): String {
216         if (bytes == 0) {
217             return "--"
218         }
219 
220         return "%.2f KB".format(bytes / 1024f)
221     }
222 }
223