<lambda>null1 package leakcanary.internal.activity.screen
2 
3 import android.content.Context
4 import android.content.res.Resources
5 import android.graphics.Bitmap
6 import android.graphics.Bitmap.Config.ARGB_8888
7 import android.graphics.Canvas
8 import android.graphics.Color
9 import android.graphics.Paint
10 import android.graphics.Paint.Style.FILL
11 import android.graphics.Paint.Style.STROKE
12 import android.graphics.Rect
13 import com.squareup.leakcanary.core.R
14 import leakcanary.internal.navigation.getColorCompat
15 import shark.HprofRecord
16 import shark.HprofRecord.HeapDumpEndRecord
17 import shark.HprofRecord.HeapDumpRecord.GcRootRecord
18 import shark.HprofRecord.HeapDumpRecord.HeapDumpInfoRecord
19 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
20 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
21 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ObjectArrayDumpRecord
22 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.BooleanArrayDump
23 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump
24 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
25 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.DoubleArrayDump
26 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.FloatArrayDump
27 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.IntArrayDump
28 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump
29 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ShortArrayDump
30 import shark.HprofRecord.LoadClassRecord
31 import shark.HprofRecord.StackTraceRecord
32 import shark.HprofRecord.StringRecord
33 import shark.StreamingHprofReader
34 import shark.StreamingRecordReaderAdapter.Companion.asStreamingRecordReader
35 import java.io.File
36 import kotlin.math.ceil
37 import kotlin.math.max
38 
39 internal object HeapDumpRenderer {
40 
41   private class HasDensity(resources: Resources) {
42     val density = resources.displayMetrics.density
43 
44     val Int.dp
45       get() = this * density
46 
47     val Float.dp
48       get() = this * density
49   }
50 
51   @Suppress("LongMethod")
52   fun render(
53     context: Context,
54     heapDumpFile: File,
55     sourceWidth: Int,
56     sourceHeight: Int,
57     /**
58      * If [sourceBytesPerPixel] > 0 then [sourceHeight] will be ignored.
59      */
60     sourceBytesPerPixel: Int
61   ): Bitmap = with(HasDensity(context.resources)) {
62     val recordPositions = mutableListOf<Pair<Int, Long>>()
63     var currentRecord: HprofRecord? = null
64 
65     val otherColor = context.getColorCompat(R.color.leak_canary_heap_other)
66     val stackTraceColor = context.getColorCompat(R.color.leak_canary_heap_stack_trace)
67     val hprofStringColor = context.getColorCompat(R.color.leak_canary_heap_hprof_string)
68     val loadClassColor = context.getColorCompat(R.color.leak_canary_heap_load_class)
69     val classDumpColor = context.getColorCompat(R.color.leak_canary_heap_class_dump)
70     val instanceColor = context.getColorCompat(R.color.leak_canary_heap_instance)
71     val objectArrayColor = context.getColorCompat(R.color.leak_canary_heap_object_array)
72     val booleanArrayColor = context.getColorCompat(R.color.leak_canary_heap_boolean_array)
73     val charArrayColor = context.getColorCompat(R.color.leak_canary_heap_char_array)
74     val floatArrayColor = context.getColorCompat(R.color.leak_canary_heap_float_array)
75     val doubleArrayColor = context.getColorCompat(R.color.leak_canary_heap_double_array)
76     val byteArrayColor = context.getColorCompat(R.color.leak_canary_heap_byte_array)
77     val shortArrayColor = context.getColorCompat(R.color.leak_canary_heap_short_array)
78     val intArrayColor = context.getColorCompat(R.color.leak_canary_heap_int_array)
79     val longArrayColor = context.getColorCompat(R.color.leak_canary_heap_long_array)
80     val colors = mapOf(
81       StringRecord::class to hprofStringColor,
82       LoadClassRecord::class to loadClassColor,
83       ClassDumpRecord::class to classDumpColor,
84       InstanceDumpRecord::class to instanceColor,
85       ObjectArrayDumpRecord::class to objectArrayColor,
86       BooleanArrayDump::class to booleanArrayColor,
87       CharArrayDump::class to charArrayColor,
88       FloatArrayDump::class to floatArrayColor,
89       DoubleArrayDump::class to doubleArrayColor,
90       ByteArrayDump::class to byteArrayColor,
91       ShortArrayDump::class to shortArrayColor,
92       IntArrayDump::class to intArrayColor,
93       LongArrayDump::class to longArrayColor,
94       StackTraceRecord::class to stackTraceColor,
95       HeapDumpEndRecord::class to otherColor,
96       GcRootRecord::class to otherColor
97     )
98 
99     val appHeapColor = context.getColorCompat(R.color.leak_canary_heap_app)
100     val imageHeapColor = context.getColorCompat(R.color.leak_canary_heap_image)
101     val zygoteHeapColor = context.getColorCompat(R.color.leak_canary_heap_zygote)
102     val stringColor = context.getColorCompat(R.color.leak_canary_heap_instance_string)
103 
104     var lastPosition = 0L
105 
106     val reader = StreamingHprofReader.readerFor(heapDumpFile).asStreamingRecordReader()
107     val hprofStringCache = mutableMapOf<Long, String>()
108     val classNames = mutableMapOf<Long, Long>()
109     reader.readRecords(
110       setOf(HprofRecord::class)
111     ) { position, record ->
112       lastPosition = position
113       when (record) {
114         is StringRecord -> {
115           hprofStringCache[record.id] = record.string
116         }
117         is LoadClassRecord -> {
118           classNames[record.id] = record.classNameStringId
119         }
120       }
121       val localCurrentRecord = currentRecord
122       when {
123         localCurrentRecord is HeapDumpInfoRecord -> {
124           val colorForHeapInfo =
125             when (hprofStringCache[localCurrentRecord.heapNameStringId]) {
126               // The primary heap on which your app allocates memory.
127               "app" -> appHeapColor
128               // The system boot image, containing classes that are preloaded during boot time.
129               // Allocations here are guaranteed to never move or go away.
130               "image" -> imageHeapColor
131               // The copy-on-write heap where an app process is forked from in the Android system.
132               "zygote" -> zygoteHeapColor
133               // JNI heap: The heap that shows where Java Native Interface (JNI) references are allocated and released.
134               // default heap: When no heap is specified by the system
135               else -> otherColor
136             }
137           recordPositions.add(colorForHeapInfo to position)
138           currentRecord = record
139         }
140         localCurrentRecord is InstanceDumpRecord
141           && hprofStringCache[classNames[localCurrentRecord.classId]] == "java.lang.String"
142           && (record !is InstanceDumpRecord || hprofStringCache[classNames[record.classId]]
143           != "java.lang.String")
144         -> {
145           recordPositions.add(stringColor to position)
146           currentRecord = record
147         }
148         currentRecord == null -> {
149           recordPositions.add(otherColor to position)
150           currentRecord = record
151         }
152         currentRecord!!::class != record::class -> {
153           recordPositions.add(colors.getValue(currentRecord!!::class) to position)
154           currentRecord = record
155         }
156       }
157     }
158     val heapLength = lastPosition
159 
160     var height: Int
161     val bytesPerPixel: Double
162 
163     if (sourceBytesPerPixel > 0) {
164       bytesPerPixel = sourceBytesPerPixel.toDouble()
165       height = ceil((heapLength / bytesPerPixel) / sourceWidth)
166         .toInt()
167     } else {
168       height = sourceHeight
169       bytesPerPixel = heapLength * 1.0 / (sourceWidth * height)
170     }
171 
172     val bitmap: Bitmap =
173       Bitmap.createBitmap(sourceWidth, height, ARGB_8888)
174 
175     val canvas = Canvas(bitmap)
176 
177     val legend = mapOf(
178       "Hprof string" to hprofStringColor,
179       "Class name" to loadClassColor,
180       "App heap" to appHeapColor,
181       "Image heap" to imageHeapColor,
182       "Zygote heap" to zygoteHeapColor,
183       "Other heap" to otherColor,
184       "Class content" to classDumpColor,
185       "Instance" to instanceColor,
186       "String" to stringColor,
187       "Object array" to objectArrayColor,
188       "Boolean array" to booleanArrayColor,
189       "Char array" to charArrayColor,
190       "Float array" to floatArrayColor,
191       "Double array" to doubleArrayColor,
192       "Byte array" to byteArrayColor,
193       "Short array" to shortArrayColor,
194       "Int array" to intArrayColor,
195       "Long array" to longArrayColor,
196       "Stack trace" to stackTraceColor,
197       "Heap End" to otherColor
198     )
199 
200     val legendTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)
201 
202     legendTextPaint.color = Color.WHITE
203     legendTextPaint.style = FILL
204     canvas.drawPaint(legendTextPaint)
205 
206     val legendSquareFillPaint = Paint()
207     legendSquareFillPaint.style = FILL
208     val legendSquareStrokePaint = Paint()
209     legendSquareStrokePaint.style = STROKE
210     legendSquareStrokePaint.strokeWidth = 0.8f.dp
211     legendSquareStrokePaint.color = Color.BLACK
212 
213     legendTextPaint.color = Color.BLACK
214     legendTextPaint.textSize = 16.dp
215 
216     val metrics = legendTextPaint.fontMetrics
217     val textHeight = metrics.descent - metrics.ascent
218 
219     val xBounds = Rect()
220     legendTextPaint.getTextBounds("x", 0, 1, xBounds)
221     val squareSize = xBounds.height()
222     val squarePaddingTop = (textHeight - squareSize) / 2
223     val squareToTextPadding = 4.dp
224     val blockToBlockPadding = 8.dp
225 
226     var maxTextWidth = 0f
227     for (name in legend.keys) {
228       maxTextWidth = max(maxTextWidth, legendTextPaint.measureText(name))
229     }
230 
231     val padding = 8.dp
232     var blockLeft = padding
233     var blockTop = padding
234     val legendWidth = sourceWidth - 2 * padding
235     for ((name, color) in legend) {
236       if (blockLeft + squareSize + squareToTextPadding + maxTextWidth > legendWidth) {
237         blockLeft = padding
238         blockTop += textHeight
239       }
240 
241       legendSquareFillPaint.color = color
242       canvas.drawRect(
243         blockLeft, blockTop + squarePaddingTop, blockLeft + squareSize,
244         blockTop + squarePaddingTop + squareSize,
245         legendSquareFillPaint
246       )
247       canvas.drawRect(
248         blockLeft, blockTop + squarePaddingTop, blockLeft + squareSize,
249         blockTop + squarePaddingTop + squareSize,
250         legendSquareStrokePaint
251       )
252       blockLeft += squareSize + squareToTextPadding
253       canvas.drawText(name, blockLeft, blockTop - metrics.ascent, legendTextPaint)
254       blockLeft += maxTextWidth
255       blockLeft += blockToBlockPadding
256     }
257     val legendHeight = blockTop + textHeight + padding
258     val source = Rect(0, 0, sourceWidth, legendHeight.toInt())
259     val destination = Rect(0, (height - legendHeight).toInt(), sourceWidth, height)
260     canvas.drawBitmap(bitmap, source, destination, null)
261     height -= legendHeight.toInt()
262 
263     val pixelPaint = Paint(Paint.ANTI_ALIAS_FLAG.inv())
264     pixelPaint.style = FILL
265 
266     var recordIndex = 0
267     for (y in 0 until height) {
268       for (x in 0 until sourceWidth) {
269         val bitmapPosition = y * sourceWidth + x
270         val heapPosition = (bitmapPosition * bytesPerPixel).toInt()
271         while (heapPosition > recordPositions[recordIndex].second && recordIndex < recordPositions.lastIndex) {
272           recordIndex++
273         }
274         pixelPaint.color = recordPositions[recordIndex].first
275         canvas.drawPoint(x.toFloat(), y.toFloat(), pixelPaint)
276       }
277     }
278     return bitmap
279   }
280 }
281