• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package leakcanary.internal
17 
18 import android.content.Context
19 import android.graphics.Bitmap
20 import android.graphics.Bitmap.Config.ARGB_8888
21 import android.graphics.Canvas
22 import android.graphics.Color
23 import android.graphics.DashPathEffect
24 import android.graphics.Paint
25 import android.graphics.PorterDuff.Mode.CLEAR
26 import android.graphics.PorterDuffXfermode
27 import android.util.AttributeSet
28 import android.view.View
29 import com.squareup.leakcanary.core.R
30 import leakcanary.internal.DisplayLeakConnectorView.Type.END
31 import leakcanary.internal.DisplayLeakConnectorView.Type.END_FIRST_UNREACHABLE
32 import leakcanary.internal.DisplayLeakConnectorView.Type.GC_ROOT
33 import leakcanary.internal.DisplayLeakConnectorView.Type.NODE_FIRST_UNREACHABLE
34 import leakcanary.internal.DisplayLeakConnectorView.Type.NODE_LAST_REACHABLE
35 import leakcanary.internal.DisplayLeakConnectorView.Type.NODE_REACHABLE
36 import leakcanary.internal.DisplayLeakConnectorView.Type.NODE_UNKNOWN
37 import leakcanary.internal.DisplayLeakConnectorView.Type.NODE_UNREACHABLE
38 import leakcanary.internal.DisplayLeakConnectorView.Type.START
39 import leakcanary.internal.DisplayLeakConnectorView.Type.START_LAST_REACHABLE
40 import leakcanary.internal.navigation.getColorCompat
41 import kotlin.math.sqrt
42 
43 internal class DisplayLeakConnectorView(
44   context: Context,
45   attrs: AttributeSet
46 ) : View(context, attrs) {
47 
48   private val classNamePaint: Paint
49   private val leakPaint: Paint
50   private val clearPaint: Paint
51   private val referencePaint: Paint
52   private val strokeSize: Float
53   private val circleY: Float
54 
55   private var type: Type? = null
56   private var cache: Bitmap? = null
57 
58   enum class Type {
59     GC_ROOT,
60     START,
61     START_LAST_REACHABLE,
62     NODE_UNKNOWN,
63     NODE_FIRST_UNREACHABLE,
64     NODE_UNREACHABLE,
65     NODE_REACHABLE,
66     NODE_LAST_REACHABLE,
67     END,
68     END_FIRST_UNREACHABLE
69   }
70 
71   init {
72 
73     val resources = resources
74 
75     type = NODE_UNKNOWN
76     circleY = resources.getDimensionPixelSize(R.dimen.leak_canary_connector_center_y)
77       .toFloat()
78     strokeSize = resources.getDimensionPixelSize(R.dimen.leak_canary_connector_stroke_size)
79       .toFloat()
80 
81     classNamePaint = Paint(Paint.ANTI_ALIAS_FLAG)
82     classNamePaint.color = context.getColorCompat(R.color.leak_canary_class_name)
83     classNamePaint.strokeWidth = strokeSize
84 
85 
86     leakPaint = Paint(Paint.ANTI_ALIAS_FLAG)
87     leakPaint.color = context.getColorCompat(R.color.leak_canary_leak)
88     leakPaint.style = Paint.Style.STROKE
89     leakPaint.strokeWidth = strokeSize
90 
91     val pathLines = resources.getDimensionPixelSize(R.dimen.leak_canary_connector_leak_dash_line)
92       .toFloat()
93 
94     val pathGaps = resources.getDimensionPixelSize(R.dimen.leak_canary_connector_leak_dash_gap)
95       .toFloat()
96     leakPaint.pathEffect = DashPathEffect(floatArrayOf(pathLines, pathGaps), 0f)
97 
98     clearPaint = Paint(Paint.ANTI_ALIAS_FLAG)
99     clearPaint.color = Color.TRANSPARENT
100     clearPaint.xfermode = CLEAR_XFER_MODE
101 
102     referencePaint = Paint(Paint.ANTI_ALIAS_FLAG)
103     referencePaint.color = context.getColorCompat(R.color.leak_canary_reference)
104     referencePaint.strokeWidth = strokeSize
105   }
106 
onDrawnull107   override fun onDraw(canvas: Canvas) {
108     val width = measuredWidth
109     val height = measuredHeight
110 
111     if (cache != null && (cache!!.width != width || cache!!.height != height)) {
112       cache!!.recycle()
113       cache = null
114     }
115 
116     if (cache == null) {
117       cache = Bitmap.createBitmap(width, height, ARGB_8888)
118 
119       val cacheCanvas = Canvas(cache!!)
120 
121       when (type) {
122         NODE_UNKNOWN -> drawItems(cacheCanvas, leakPaint, leakPaint)
123         NODE_UNREACHABLE, NODE_REACHABLE -> drawItems(
124           cacheCanvas, referencePaint, referencePaint
125         )
126         NODE_FIRST_UNREACHABLE -> drawItems(
127           cacheCanvas, leakPaint, referencePaint
128         )
129         NODE_LAST_REACHABLE -> drawItems(
130           cacheCanvas, referencePaint, leakPaint
131         )
132         START -> {
133           drawStartLine(cacheCanvas)
134           drawItems(cacheCanvas, null, referencePaint)
135         }
136         START_LAST_REACHABLE -> {
137           drawStartLine(cacheCanvas)
138           drawItems(cacheCanvas, null, leakPaint)
139         }
140         END -> drawItems(cacheCanvas, referencePaint, null)
141         END_FIRST_UNREACHABLE -> drawItems(
142           cacheCanvas, leakPaint, null
143         )
144         GC_ROOT -> drawGcRoot(cacheCanvas)
145         else -> throw UnsupportedOperationException("Unknown type " + type!!)
146       }
147     }
148     canvas.drawBitmap(cache!!, 0f, 0f, null)
149   }
150 
drawStartLinenull151   private fun drawStartLine(cacheCanvas: Canvas) {
152     val width = measuredWidth
153     val halfWidth = width / 2f
154     cacheCanvas.drawLine(halfWidth, 0f, halfWidth, circleY, classNamePaint)
155   }
156 
drawGcRootnull157   private fun drawGcRoot(
158     cacheCanvas: Canvas
159   ) {
160     val width = measuredWidth
161     val height = measuredHeight
162     val halfWidth = width / 2f
163     cacheCanvas.drawLine(halfWidth, 0f, halfWidth, height.toFloat(), classNamePaint)
164   }
165 
drawItemsnull166   private fun drawItems(
167     cacheCanvas: Canvas,
168     arrowHeadPaint: Paint?,
169     nextArrowPaint: Paint?
170   ) {
171     if (arrowHeadPaint != null) {
172       drawArrowHead(cacheCanvas, arrowHeadPaint)
173     }
174     if (nextArrowPaint != null) {
175       drawNextArrowLine(cacheCanvas, nextArrowPaint)
176     }
177     drawInstanceCircle(cacheCanvas)
178   }
179 
drawArrowHeadnull180   private fun drawArrowHead(
181     cacheCanvas: Canvas,
182     paint: Paint
183   ) {
184     // Circle center is at half height
185     val width = measuredWidth
186     val halfWidth = width / 2f
187     val circleRadius = width / 3f
188 // Splitting the arrow head in two makes an isosceles right triangle.
189     // It's hypotenuse is side * sqrt(2)
190     val arrowHeight = halfWidth / 2 * SQRT_TWO
191     val halfStrokeSize = strokeSize / 2
192     val translateY = circleY - arrowHeight - circleRadius * 2 - strokeSize
193 
194     val lineYEnd = circleY - circleRadius - strokeSize / 2
195     cacheCanvas.drawLine(halfWidth, 0f, halfWidth, lineYEnd, paint)
196     cacheCanvas.translate(halfWidth, translateY)
197     cacheCanvas.rotate(45f)
198     cacheCanvas.drawLine(
199       0f, halfWidth, halfWidth + halfStrokeSize, halfWidth,
200       paint
201     )
202     cacheCanvas.drawLine(halfWidth, 0f, halfWidth, halfWidth, paint)
203     cacheCanvas.rotate(-45f)
204     cacheCanvas.translate(-halfWidth, -translateY)
205   }
206 
drawNextArrowLinenull207   private fun drawNextArrowLine(
208     cacheCanvas: Canvas,
209     paint: Paint
210   ) {
211     val height = measuredHeight
212     val width = measuredWidth
213     val centerX = width / 2f
214     cacheCanvas.drawLine(centerX, circleY, centerX, height.toFloat(), paint)
215   }
216 
drawInstanceCirclenull217   private fun drawInstanceCircle(cacheCanvas: Canvas) {
218     val width = measuredWidth
219     val circleX = width / 2f
220     val circleRadius = width / 3f
221     cacheCanvas.drawCircle(circleX, circleY, circleRadius, classNamePaint)
222   }
223 
setTypenull224   fun setType(type: Type) {
225     if (type != this.type) {
226       this.type = type
227       if (cache != null) {
228         cache!!.recycle()
229         cache = null
230       }
231       invalidate()
232     }
233   }
234 
235   companion object {
236 
237     private val SQRT_TWO = sqrt(2.0)
238       .toFloat()
239     private val CLEAR_XFER_MODE = PorterDuffXfermode(CLEAR)
240   }
241 }
242