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 java.io.PrintWriter 29 import javax.inject.Inject 30 31 /** Dumps current notification memory use to bug reports for easier debugging. */ 32 @SysUISingleton 33 class NotificationMemoryDumper 34 @Inject 35 constructor(val dumpManager: DumpManager, val notificationPipeline: NotifPipeline) : Dumpable { 36 37 fun init() { 38 dumpManager.registerNormalDumpable(javaClass.simpleName, this) 39 Log.i("NotificationMemory", "Registered dumpable.") 40 } 41 42 override fun dump(pw: PrintWriter, args: Array<out String>) { 43 val memoryUse = 44 NotificationMemoryMeter.notificationMemoryUse(notificationPipeline.allNotifs) 45 .sortedWith(compareBy({ it.packageName }, { it.notificationKey })) 46 dumpNotificationObjects(pw, memoryUse) 47 dumpNotificationViewUsage(pw, memoryUse) 48 } 49 50 /** Renders a table of notification object usage into passed [PrintWriter]. */ 51 private fun dumpNotificationObjects(pw: PrintWriter, memoryUse: List<NotificationMemoryUsage>) { 52 val columns = 53 listOf( 54 "Package", 55 "Small Icon", 56 "Large Icon", 57 "Style", 58 "Style Icon", 59 "Big Picture", 60 "Extender", 61 "Extras", 62 "Custom View", 63 "Key" 64 ) 65 val rows: List<Row> = 66 memoryUse.map { 67 listOf( 68 it.packageName, 69 toKb(it.objectUsage.smallIcon), 70 toKb(it.objectUsage.largeIcon), 71 styleEnumToString(it.objectUsage.style), 72 toKb(it.objectUsage.styleIcon), 73 toKb(it.objectUsage.bigPicture), 74 toKb(it.objectUsage.extender), 75 toKb(it.objectUsage.extras), 76 it.objectUsage.hasCustomView.toString(), 77 // | is a field delimiter in the output format so we need to replace 78 // it to avoid breakage. 79 it.notificationKey.replace('|', '│') 80 ) 81 } 82 83 // Calculate totals for easily glanceable summary. 84 data class Totals( 85 var smallIcon: Int = 0, 86 var largeIcon: Int = 0, 87 var styleIcon: Int = 0, 88 var bigPicture: Int = 0, 89 var extender: Int = 0, 90 var extras: Int = 0, 91 ) 92 93 val totals = 94 memoryUse.fold(Totals()) { t, usage -> 95 t.smallIcon += usage.objectUsage.smallIcon 96 t.largeIcon += usage.objectUsage.largeIcon 97 t.styleIcon += usage.objectUsage.styleIcon 98 t.bigPicture += usage.objectUsage.bigPicture 99 t.extender += usage.objectUsage.extender 100 t.extras += usage.objectUsage.extras 101 t 102 } 103 104 val totalsRow: List<Row> = 105 listOf( 106 listOf( 107 "TOTALS", 108 toKb(totals.smallIcon), 109 toKb(totals.largeIcon), 110 "", 111 toKb(totals.styleIcon), 112 toKb(totals.bigPicture), 113 toKb(totals.extender), 114 toKb(totals.extras), 115 "", 116 "" 117 ) 118 ) 119 val tableLogger = DumpsysTableLogger("Notification Object Usage", columns, rows + totalsRow) 120 tableLogger.printTableData(pw) 121 } 122 123 /** Renders a table of notification view usage into passed [PrintWriter] */ 124 private fun dumpNotificationViewUsage( 125 pw: PrintWriter, 126 memoryUse: List<NotificationMemoryUsage>, 127 ) { 128 129 data class Totals( 130 var smallIcon: Int = 0, 131 var largeIcon: Int = 0, 132 var style: Int = 0, 133 var customViews: Int = 0, 134 var softwareBitmapsPenalty: Int = 0, 135 ) 136 137 val columns = 138 listOf( 139 "Package", 140 "View Type", 141 "Small Icon", 142 "Large Icon", 143 "Style Use", 144 "Custom View", 145 "Software Bitmaps", 146 "Key" 147 ) 148 val rows = 149 memoryUse 150 .filter { it.viewUsage.isNotEmpty() } 151 .flatMap { use -> 152 use.viewUsage.map { view -> 153 listOf( 154 use.packageName, 155 view.viewType.toString(), 156 toKb(view.smallIcon), 157 toKb(view.largeIcon), 158 toKb(view.style), 159 toKb(view.customViews), 160 toKb(view.softwareBitmapsPenalty), 161 // | is a field delimiter in the output format so we need to replace 162 // it to avoid breakage. 163 use.notificationKey.replace('|', '│') 164 ) 165 } 166 } 167 168 val totals = Totals() 169 memoryUse 170 .filter { it.viewUsage.isNotEmpty() } 171 .map { it.viewUsage.firstOrNull { view -> view.viewType == ViewType.TOTAL } } 172 .filterNotNull() 173 .forEach { view -> 174 totals.smallIcon += view.smallIcon 175 totals.largeIcon += view.largeIcon 176 totals.style += view.style 177 totals.customViews += view.customViews 178 totals.softwareBitmapsPenalty += view.softwareBitmapsPenalty 179 } 180 181 val totalsRow: List<Row> = 182 listOf( 183 listOf( 184 "TOTALS", 185 "", 186 toKb(totals.smallIcon), 187 toKb(totals.largeIcon), 188 toKb(totals.style), 189 toKb(totals.customViews), 190 toKb(totals.softwareBitmapsPenalty), 191 "" 192 ) 193 ) 194 val tableLogger = DumpsysTableLogger("Notification View Usage", columns, rows + totalsRow) 195 tableLogger.printTableData(pw) 196 } 197 198 private fun styleEnumToString(styleEnum: Int): String = 199 when (styleEnum) { 200 NotificationEnums.STYLE_UNSPECIFIED -> "Unspecified" 201 NotificationEnums.STYLE_NONE -> "None" 202 NotificationEnums.STYLE_BIG_PICTURE -> "BigPicture" 203 NotificationEnums.STYLE_BIG_TEXT -> "BigText" 204 NotificationEnums.STYLE_CALL -> "Call" 205 NotificationEnums.STYLE_DECORATED_CUSTOM_VIEW -> "DCustomView" 206 NotificationEnums.STYLE_INBOX -> "Inbox" 207 NotificationEnums.STYLE_MEDIA -> "Media" 208 NotificationEnums.STYLE_MESSAGING -> "Messaging" 209 NotificationEnums.STYLE_RANKER_GROUP -> "RankerGroup" 210 else -> "Unknown" 211 } 212 213 private fun toKb(bytes: Int): String { 214 if (bytes == 0) { 215 return "--" 216 } 217 218 return "%.2f KB".format(bytes / 1024f) 219 } 220 } 221