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