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