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.Matrix
23 import android.graphics.Path
24 import android.graphics.drawable.AdaptiveIconDrawable
25 import android.graphics.drawable.ColorDrawable
26 import android.graphics.drawable.InsetDrawable
27 import android.util.PathParser
28 import android.util.TypedValue
29 import android.view.LayoutInflater
30 import android.widget.FrameLayout
31 import com.android.wm.shell.R
32 
33 class BubbleOverflow(
34     private val context: Context,
35     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 overflowBtn: BadgedImageView?
48 
49     init {
50         updateResources()
51         expandedView = null
52         overflowBtn = null
53     }
54 
55     /** Call before use and again if cleanUpExpandedState was called.  */
56     fun initialize(controller: BubbleController) {
57         getExpandedView()?.initialize(controller, controller.stackView, true /* isOverflow */)
58     }
59 
60     fun cleanUpExpandedState() {
61         expandedView?.cleanUpExpandedState()
62         expandedView = null
63     }
64 
65     fun update() {
66         updateResources()
67         getExpandedView()?.applyThemeAttrs()
68         // Apply inset and new style to fresh icon drawable.
69         getIconView()?.setImageResource(R.drawable.bubble_ic_overflow_button)
70         updateBtnTheme()
71     }
72 
73     fun updateResources() {
74         overflowIconInset = context.resources.getDimensionPixelSize(
75                 R.dimen.bubble_overflow_icon_inset)
76         overflowBtn?.layoutParams = FrameLayout.LayoutParams(positioner.bubbleSize,
77                 positioner.bubbleSize)
78         expandedView?.updateDimensions()
79     }
80 
81     private fun updateBtnTheme() {
82         val res = context.resources
83 
84         // Set overflow button accent color, dot color
85         val typedValue = TypedValue()
86         context.theme.resolveAttribute(com.android.internal.R.attr.colorAccentPrimary,
87                 typedValue, true)
88         val colorAccent = res.getColor(typedValue.resourceId, null)
89         dotColor = colorAccent
90 
91         val shapeColor = res.getColor(android.R.color.system_accent1_1000)
92         overflowBtn?.drawable?.setTint(shapeColor)
93 
94         val iconFactory = BubbleIconFactory(context)
95 
96         // Update bitmap
97         val fg = InsetDrawable(overflowBtn?.drawable, overflowIconInset)
98         bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(
99                 ColorDrawable(colorAccent), fg),
100             null /* user */, true /* shrinkNonAdaptiveIcons */).icon
101 
102         // Update dot path
103         dotPath = PathParser.createPathFromPathData(
104             res.getString(com.android.internal.R.string.config_icon_mask))
105         val scale = iconFactory.normalizer.getScale(iconView!!.drawable,
106             null /* outBounds */, null /* path */, null /* outMaskShape */)
107         val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
108         val matrix = Matrix()
109         matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
110             radius /* pivot y */)
111         dotPath.transform(matrix)
112 
113         // Attach BubbleOverflow to BadgedImageView
114         overflowBtn?.setRenderedBubble(this)
115         overflowBtn?.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE)
116     }
117 
118     fun setVisible(visible: Int) {
119         overflowBtn?.visibility = visible
120     }
121 
122     fun setShowDot(show: Boolean) {
123         showDot = show
124         overflowBtn?.updateDotVisibility(true /* animate */)
125     }
126 
127     override fun getExpandedView(): BubbleExpandedView? {
128         if (expandedView == null) {
129             expandedView = inflater.inflate(R.layout.bubble_expanded_view,
130                     null /* root */, false /* attachToRoot */) as BubbleExpandedView
131             expandedView?.applyThemeAttrs()
132             updateResources()
133         }
134         return expandedView
135     }
136 
137     override fun getDotColor(): Int {
138         return dotColor
139     }
140 
141     override fun getAppBadge(): Bitmap? {
142         return null
143     }
144 
145     override fun getBubbleIcon(): Bitmap {
146         return bitmap
147     }
148 
149     override fun showDot(): Boolean {
150         return showDot
151     }
152 
153     override fun getDotPath(): Path? {
154         return dotPath
155     }
156 
157     override fun setTaskViewVisibility(visible: Boolean) {
158         // Overflow does not have a TaskView.
159     }
160 
161     override fun getIconView(): BadgedImageView? {
162         if (overflowBtn == null) {
163             overflowBtn = inflater.inflate(R.layout.bubble_overflow_button,
164                     null /* root */, false /* attachToRoot */) as BadgedImageView
165             overflowBtn?.initialize(positioner)
166             overflowBtn?.contentDescription = context.resources.getString(
167                     R.string.bubble_overflow_button_content_description)
168             val bubbleSize = positioner.bubbleSize
169             overflowBtn?.layoutParams = FrameLayout.LayoutParams(bubbleSize, bubbleSize)
170             updateBtnTheme()
171         }
172         return overflowBtn
173     }
174 
175     override fun getKey(): String {
176         return KEY
177     }
178 
179     override fun getTaskId(): Int {
180         return if (expandedView != null) expandedView!!.taskId else INVALID_TASK_ID
181     }
182 
183     companion object {
184         const val KEY = "Overflow"
185     }
186 }