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