1 /*
2  * Copyright (C) 2022 The Android Open Source Project
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 com.android.systemui.shared.shadow
17 
18 import android.content.res.ColorStateList
19 import android.graphics.BlendMode
20 import android.graphics.Canvas
21 import android.graphics.Color
22 import android.graphics.ColorFilter
23 import android.graphics.PixelFormat
24 import android.graphics.PorterDuff
25 import android.graphics.PorterDuffColorFilter
26 import android.graphics.RenderEffect
27 import android.graphics.RenderNode
28 import android.graphics.Shader
29 import android.graphics.drawable.Drawable
30 import android.graphics.drawable.InsetDrawable
31 import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo
32 
33 /** A component to draw an icon with two layers of shadows. */
34 class DoubleShadowIconDrawable(
35     keyShadowInfo: ShadowInfo,
36     ambientShadowInfo: ShadowInfo,
37     iconDrawable: Drawable,
38     iconSize: Int,
39     val iconInsetSize: Int
40 ) : Drawable() {
41     private val mAmbientShadowInfo: ShadowInfo
42     private val mCanvasSize: Int
43     private val mKeyShadowInfo: ShadowInfo
44     private val mIconDrawable: InsetDrawable
45     private val mDoubleShadowNode: RenderNode
46 
47     init {
48         mCanvasSize = iconSize + iconInsetSize * 2
49         mKeyShadowInfo = keyShadowInfo
50         mAmbientShadowInfo = ambientShadowInfo
51         setBounds(0, 0, mCanvasSize, mCanvasSize)
52         mIconDrawable = InsetDrawable(iconDrawable, iconInsetSize)
53         mIconDrawable.setBounds(0, 0, mCanvasSize, mCanvasSize)
54         mDoubleShadowNode = createShadowRenderNode()
55     }
56 
57     private fun createShadowRenderNode(): RenderNode {
58         val renderNode = RenderNode("DoubleShadowNode")
59         renderNode.setPosition(0, 0, mCanvasSize, mCanvasSize)
60         // Create render effects
61         val ambientShadow =
62             createShadowRenderEffect(
63                 mAmbientShadowInfo.blur,
64                 mAmbientShadowInfo.offsetX,
65                 mAmbientShadowInfo.offsetY,
66                 mAmbientShadowInfo.alpha
67             )
68         val keyShadow =
69             createShadowRenderEffect(
70                 mKeyShadowInfo.blur,
71                 mKeyShadowInfo.offsetX,
72                 mKeyShadowInfo.offsetY,
73                 mKeyShadowInfo.alpha
74             )
75         val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DST_ATOP)
76         renderNode.setRenderEffect(blend)
77         return renderNode
78     }
79 
80     private fun createShadowRenderEffect(
81         radius: Float,
82         offsetX: Float,
83         offsetY: Float,
84         alpha: Float
85     ): RenderEffect {
86         return RenderEffect.createColorFilterEffect(
87             PorterDuffColorFilter(Color.argb(alpha, 0f, 0f, 0f), PorterDuff.Mode.MULTIPLY),
88             RenderEffect.createOffsetEffect(
89                 offsetX,
90                 offsetY,
91                 RenderEffect.createBlurEffect(radius, radius, Shader.TileMode.CLAMP)
92             )
93         )
94     }
95 
96     override fun draw(canvas: Canvas) {
97         if (canvas.isHardwareAccelerated) {
98             if (!mDoubleShadowNode.hasDisplayList()) {
99                 // Record render node if its display list is not recorded or discarded
100                 // (which happens when it's no longer drawn by anything).
101                 val recordingCanvas = mDoubleShadowNode.beginRecording()
102                 mIconDrawable.draw(recordingCanvas)
103                 mDoubleShadowNode.endRecording()
104             }
105             canvas.drawRenderNode(mDoubleShadowNode)
106         }
107         mIconDrawable.draw(canvas)
108     }
109 
110     override fun getIntrinsicHeight(): Int {
111         return mCanvasSize
112     }
113 
114     override fun getIntrinsicWidth(): Int {
115         return mCanvasSize
116     }
117 
118     override fun getOpacity(): Int {
119         return PixelFormat.TRANSPARENT
120     }
121 
122     override fun setAlpha(alpha: Int) {
123         mIconDrawable.alpha = alpha
124     }
125 
126     override fun setColorFilter(colorFilter: ColorFilter?) {
127         mIconDrawable.colorFilter = colorFilter
128     }
129 
130     override fun setTint(color: Int) {
131         mIconDrawable.setTint(color)
132     }
133 
134     override fun setTintList(tint: ColorStateList?) {
135         mIconDrawable.setTintList(tint)
136     }
137 }
138