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