1 /*
2  * Copyright (C) 2020 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.wm.shell.bubbles
18 
19 import android.app.ActivityTaskManager.INVALID_TASK_ID
20 import android.content.Context
21 import android.graphics.Bitmap
22 import android.graphics.Color
23 import android.graphics.Matrix
24 import android.graphics.Path
25 import android.graphics.drawable.AdaptiveIconDrawable
26 import android.graphics.drawable.ColorDrawable
27 import android.graphics.drawable.InsetDrawable
28 import android.util.PathParser
29 import android.view.LayoutInflater
30 import android.view.View.VISIBLE
31 import android.widget.FrameLayout
32 import androidx.core.content.ContextCompat
33 import com.android.launcher3.icons.BubbleIconFactory
34 import com.android.wm.shell.R
35 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView
36 
37 class BubbleOverflow(private val context: Context, private val positioner: BubblePositioner) :
38     BubbleViewProvider {
39 
40     private lateinit var bitmap: Bitmap
41     private lateinit var dotPath: Path
42 
43     private var dotColor = 0
44     private var showDot = false
45     private var overflowIconInset = 0
46 
47     private val inflater: LayoutInflater = LayoutInflater.from(context)
48     private var expandedView: BubbleExpandedView?
49     private var bubbleBarExpandedView: BubbleBarExpandedView? = null
50     private var overflowBtn: BadgedImageView?
51 
52     init {
53         updateResources()
54         expandedView = null
55         overflowBtn = null
56     }
57 
58     /** Call before use and again if cleanUpExpandedState was called. */
initializenull59     fun initialize(
60         expandedViewManager: BubbleExpandedViewManager,
61         stackView: BubbleStackView,
62         positioner: BubblePositioner
63     ) {
64         createExpandedView()
65                 .initialize(
66                         expandedViewManager,
67                     stackView,
68                     positioner,
69                     /* isOverflow= */ true,
70                     /* bubbleTaskView= */ null
71                 )
72     }
73 
initializeForBubbleBarnull74     fun initializeForBubbleBar(
75         expandedViewManager: BubbleExpandedViewManager,
76         positioner: BubblePositioner
77     ) {
78         createBubbleBarExpandedView()
79             .initialize(
80                 expandedViewManager,
81                 positioner,
82                 /* isOverflow= */ true,
83                 /* bubbleTaskView= */ null
84             )
85     }
86 
cleanUpExpandedStatenull87     fun cleanUpExpandedState() {
88         expandedView?.cleanUpExpandedState()
89         expandedView = null
90         bubbleBarExpandedView?.cleanUpExpandedState()
91         bubbleBarExpandedView = null
92     }
93 
updatenull94     fun update() {
95         updateResources()
96         getExpandedView()?.applyThemeAttrs()
97         getBubbleBarExpandedView()?.applyThemeAttrs()
98         // Apply inset and new style to fresh icon drawable.
99         getIconView()?.setIconImageResource(R.drawable.bubble_ic_overflow_button)
100         updateBtnTheme()
101     }
102 
updateResourcesnull103     fun updateResources() {
104         overflowIconInset =
105             context.resources.getDimensionPixelSize(R.dimen.bubble_overflow_icon_inset)
106         overflowBtn?.layoutParams =
107             FrameLayout.LayoutParams(positioner.bubbleSize, positioner.bubbleSize)
108         expandedView?.updateDimensions()
109     }
110 
updateBtnThemenull111     private fun updateBtnTheme() {
112         val res = context.resources
113 
114         // Set overflow button accent color, dot color
115 
116         val typedArray =
117             context.obtainStyledAttributes(
118                 intArrayOf(
119                     com.android.internal.R.attr.materialColorPrimaryFixed,
120                     com.android.internal.R.attr.materialColorOnPrimaryFixed
121                 )
122             )
123 
124         val colorAccent = typedArray.getColor(0, Color.WHITE)
125         val shapeColor = typedArray.getColor(1, Color.BLACK)
126         typedArray.recycle()
127 
128         dotColor = colorAccent
129         overflowBtn?.iconDrawable?.setTint(shapeColor)
130 
131         val iconFactory =
132             BubbleIconFactory(
133                 context,
134                 res.getDimensionPixelSize(R.dimen.bubble_size),
135                 res.getDimensionPixelSize(R.dimen.bubble_badge_size),
136                 ContextCompat.getColor(
137                     context,
138                     com.android.launcher3.icons.R.color.important_conversation
139                 ),
140                 res.getDimensionPixelSize(com.android.internal.R.dimen.importance_ring_stroke_width)
141             )
142 
143         // Update bitmap
144         val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset)
145         bitmap =
146             iconFactory
147                 .createBadgedIconBitmap(AdaptiveIconDrawable(ColorDrawable(colorAccent), fg))
148                 .icon
149 
150         // Update dot path
151         dotPath =
152             PathParser.createPathFromPathData(
153                 res.getString(com.android.internal.R.string.config_icon_mask)
154             )
155         val scale =
156             iconFactory.normalizer.getScale(
157                 iconView!!.iconDrawable,
158                 null /* outBounds */,
159                 null /* path */,
160                 null /* outMaskShape */
161             )
162         val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
163         val matrix = Matrix()
164         matrix.setScale(
165             scale /* x scale */,
166             scale /* y scale */,
167             radius /* pivot x */,
168             radius /* pivot y */
169         )
170         dotPath.transform(matrix)
171 
172         // Attach BubbleOverflow to BadgedImageView
173         overflowBtn?.setRenderedBubble(this)
174         overflowBtn?.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE)
175     }
176 
setVisiblenull177     fun setVisible(visible: Int) {
178         overflowBtn?.visibility = visible
179     }
180 
setShowDotnull181     fun setShowDot(show: Boolean) {
182         showDot = show
183         if (overflowBtn?.visibility == VISIBLE) {
184             overflowBtn?.updateDotVisibility(true /* animate */)
185         }
186     }
187 
188     /** Creates the expanded view for bubbles showing in the stack view. */
createExpandedViewnull189     private fun createExpandedView(): BubbleExpandedView {
190         val view =
191             inflater.inflate(
192                 R.layout.bubble_expanded_view,
193                 null /* root */,
194                 false /* attachToRoot */
195             ) as BubbleExpandedView
196         view.applyThemeAttrs()
197         expandedView = view
198         updateResources()
199         return view
200     }
201 
getExpandedViewnull202     override fun getExpandedView(): BubbleExpandedView? {
203         return expandedView
204     }
205 
206     /** Creates the expanded view for bubbles showing in the bubble bar. */
createBubbleBarExpandedViewnull207     private fun createBubbleBarExpandedView(): BubbleBarExpandedView {
208         val view =
209             inflater.inflate(
210                 R.layout.bubble_bar_expanded_view,
211                 null, /* root */
212                 false /* attachToRoot*/
213             ) as BubbleBarExpandedView
214         view.applyThemeAttrs()
215         bubbleBarExpandedView = view
216         return view
217     }
218 
getBubbleBarExpandedViewnull219     override fun getBubbleBarExpandedView(): BubbleBarExpandedView? = bubbleBarExpandedView
220 
221     override fun getDotColor(): Int {
222         return dotColor
223     }
224 
getAppBadgenull225     override fun getAppBadge(): Bitmap? {
226         return null
227     }
228 
getRawAppBadgenull229     override fun getRawAppBadge(): Bitmap? {
230         return null
231     }
232 
getBubbleIconnull233     override fun getBubbleIcon(): Bitmap {
234         return bitmap
235     }
236 
showDotnull237     override fun showDot(): Boolean {
238         return showDot
239     }
240 
getDotPathnull241     override fun getDotPath(): Path? {
242         return dotPath
243     }
244 
setTaskViewVisibilitynull245     override fun setTaskViewVisibility(visible: Boolean) {
246         // Overflow does not have a TaskView.
247     }
248 
getIconViewnull249     override fun getIconView(): BadgedImageView? {
250         if (overflowBtn == null) {
251             overflowBtn =
252                 inflater.inflate(
253                     R.layout.bubble_overflow_button,
254                     null /* root */,
255                     false /* attachToRoot */
256                 ) as BadgedImageView
257             overflowBtn?.initialize(positioner)
258             overflowBtn?.contentDescription =
259                 context.resources.getString(R.string.bubble_overflow_button_content_description)
260             val bubbleSize = positioner.bubbleSize
261             overflowBtn?.layoutParams = FrameLayout.LayoutParams(bubbleSize, bubbleSize)
262             updateBtnTheme()
263         }
264         return overflowBtn
265     }
266 
getKeynull267     override fun getKey(): String {
268         return KEY
269     }
270 
getTaskIdnull271     override fun getTaskId(): Int {
272         return if (expandedView != null) expandedView!!.taskId else INVALID_TASK_ID
273     }
274 
275     companion object {
276         const val KEY = "Overflow"
277     }
278 }
279