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