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. */ initializenull56 fun initialize(controller: BubbleController) { 57 getExpandedView()?.initialize(controller, controller.stackView, true /* isOverflow */) 58 } 59 cleanUpExpandedStatenull60 fun cleanUpExpandedState() { 61 expandedView?.cleanUpExpandedState() 62 expandedView = null 63 } 64 updatenull65 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 updateResourcesnull73 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 updateBtnThemenull81 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 setVisiblenull118 fun setVisible(visible: Int) { 119 overflowBtn?.visibility = visible 120 } 121 setShowDotnull122 fun setShowDot(show: Boolean) { 123 showDot = show 124 overflowBtn?.updateDotVisibility(true /* animate */) 125 } 126 getExpandedViewnull127 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 getDotColornull137 override fun getDotColor(): Int { 138 return dotColor 139 } 140 getAppBadgenull141 override fun getAppBadge(): Bitmap? { 142 return null 143 } 144 getBubbleIconnull145 override fun getBubbleIcon(): Bitmap { 146 return bitmap 147 } 148 showDotnull149 override fun showDot(): Boolean { 150 return showDot 151 } 152 getDotPathnull153 override fun getDotPath(): Path? { 154 return dotPath 155 } 156 setExpandedContentAlphanull157 override fun setExpandedContentAlpha(alpha: Float) { 158 expandedView?.alpha = alpha 159 } 160 setTaskViewVisibilitynull161 override fun setTaskViewVisibility(visible: Boolean) { 162 // Overflow does not have a TaskView. 163 } 164 getIconViewnull165 override fun getIconView(): BadgedImageView? { 166 if (overflowBtn == null) { 167 overflowBtn = inflater.inflate(R.layout.bubble_overflow_button, 168 null /* root */, false /* attachToRoot */) as BadgedImageView 169 overflowBtn?.initialize(positioner) 170 overflowBtn?.contentDescription = context.resources.getString( 171 R.string.bubble_overflow_button_content_description) 172 val bubbleSize = positioner.bubbleSize 173 overflowBtn?.layoutParams = FrameLayout.LayoutParams(bubbleSize, bubbleSize) 174 updateBtnTheme() 175 } 176 return overflowBtn 177 } 178 getKeynull179 override fun getKey(): String { 180 return KEY 181 } 182 getTaskIdnull183 override fun getTaskId(): Int { 184 return if (expandedView != null) expandedView!!.taskId else INVALID_TASK_ID 185 } 186 187 companion object { 188 const val KEY = "Overflow" 189 } 190 }