1 /*
2  * Copyright (C) 2024 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 
17 package com.android.launcher3.views
18 
19 import android.content.res.ColorStateList
20 import android.graphics.BlendMode
21 import android.graphics.Canvas
22 import android.graphics.Color
23 import android.graphics.ColorFilter
24 import android.graphics.PixelFormat
25 import android.graphics.PorterDuff
26 import android.graphics.PorterDuffColorFilter
27 import android.graphics.RenderEffect
28 import android.graphics.RenderNode
29 import android.graphics.Shader
30 import android.graphics.drawable.Drawable
31 import android.graphics.drawable.InsetDrawable
32 import android.os.Build.VERSION_CODES
33 import androidx.annotation.RequiresApi
34 import androidx.annotation.VisibleForTesting
35 
36 /**
37  * Launcher wrapper for Drawables to provide a double shadow effect. Currently for use with
38  * [DoubleShadowBubbleTextView] to provide a similar shadow to inline icons.
39  */
40 @RequiresApi(VERSION_CODES.S)
41 class DoubleShadowIconDrawable(
42     private val shadowInfo: ShadowInfo,
43     iconDrawable: Drawable,
44     private val iconSize: Int,
45     iconInsetSize: Int
46 ) : Drawable() {
47     private val mIconDrawable: InsetDrawable
48     private val mDoubleShadowNode: RenderNode?
49 
50     init {
51         mIconDrawable = InsetDrawable(iconDrawable, iconInsetSize)
52         mIconDrawable.setBounds(0, 0, iconSize, iconSize)
53         mDoubleShadowNode = createShadowRenderNode()
54     }
55 
56     @VisibleForTesting
createShadowRenderNodenull57     fun createShadowRenderNode(): RenderNode {
58         val renderNode = RenderNode("DoubleShadowNode")
59         renderNode.setPosition(0, 0, iconSize, iconSize)
60         // Create render effects
61         val ambientShadow =
62             createShadowRenderEffect(
63                 shadowInfo.ambientShadowBlur,
64                 0f,
65                 0f,
66                 Color.alpha(shadowInfo.ambientShadowColor).toFloat()
67             )
68         val keyShadow =
69             createShadowRenderEffect(
70                 shadowInfo.keyShadowBlur,
71                 shadowInfo.keyShadowOffsetX,
72                 shadowInfo.keyShadowOffsetY,
73                 Color.alpha(shadowInfo.keyShadowColor).toFloat()
74             )
75         val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DST_ATOP)
76         renderNode.setRenderEffect(blend)
77         return renderNode
78     }
79 
80     @VisibleForTesting
createShadowRenderEffectnull81     fun createShadowRenderEffect(
82         radius: Float,
83         offsetX: Float,
84         offsetY: Float,
85         alpha: Float
86     ): RenderEffect {
87         return RenderEffect.createColorFilterEffect(
88             PorterDuffColorFilter(Color.argb(alpha, 0f, 0f, 0f), PorterDuff.Mode.MULTIPLY),
89             RenderEffect.createOffsetEffect(
90                 offsetX,
91                 offsetY,
92                 RenderEffect.createBlurEffect(radius, radius, Shader.TileMode.CLAMP)
93             )
94         )
95     }
96 
drawnull97     override fun draw(canvas: Canvas) {
98         if (canvas.isHardwareAccelerated && mDoubleShadowNode != null) {
99             if (!mDoubleShadowNode.hasDisplayList()) {
100                 // Record render node if its display list is not recorded or discarded
101                 // (which happens when it's no longer drawn by anything).
102                 val recordingCanvas = mDoubleShadowNode.beginRecording()
103                 mIconDrawable.draw(recordingCanvas)
104                 mDoubleShadowNode.endRecording()
105             }
106             canvas.drawRenderNode(mDoubleShadowNode)
107         }
108         mIconDrawable.draw(canvas)
109     }
110 
getIntrinsicHeightnull111     override fun getIntrinsicHeight() = iconSize
112 
113     override fun getIntrinsicWidth() = iconSize
114 
115     override fun getOpacity() = PixelFormat.TRANSPARENT
116 
117     override fun setAlpha(alpha: Int) {
118         mIconDrawable.alpha = alpha
119     }
120 
setColorFilternull121     override fun setColorFilter(colorFilter: ColorFilter?) {
122         mIconDrawable.colorFilter = colorFilter
123     }
124 
setTintnull125     override fun setTint(color: Int) {
126         mIconDrawable.setTint(color)
127     }
128 
setTintListnull129     override fun setTintList(tint: ColorStateList?) {
130         mIconDrawable.setTintList(tint)
131     }
132 }
133