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 }