1 /* 2 * Copyright (C) 2023 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.systemui.controls.ui 18 19 import android.content.Context 20 import android.content.res.Resources 21 import android.graphics.drawable.ColorDrawable 22 import android.graphics.drawable.Drawable 23 import android.view.Gravity.END 24 import android.view.Gravity.GravityFlags 25 import android.view.Gravity.NO_GRAVITY 26 import android.view.Gravity.START 27 import android.view.View 28 import android.view.View.MeasureSpec 29 import android.view.ViewGroup 30 import android.widget.ListPopupWindow 31 import android.widget.ListView 32 import android.widget.PopupWindow 33 import com.android.systemui.res.R 34 import kotlin.math.max 35 36 class ControlsPopupMenu(context: Context) : ListPopupWindow(context) { 37 38 private val resources: Resources = context.resources 39 40 private val listDividerHeight: Int = 41 resources.getDimensionPixelSize(R.dimen.control_popup_items_divider_height) 42 private val horizontalMargin: Int = 43 resources.getDimensionPixelSize(R.dimen.control_popup_horizontal_margin) 44 private val maxWidth: Int = resources.getDimensionPixelSize(R.dimen.control_popup_max_width) 45 46 private val dialogBackground: Drawable = resources.getDrawable(R.drawable.controls_popup_bg)!! 47 private val dimDrawable: Drawable = ColorDrawable(resources.getColor(R.color.control_popup_dim)) 48 49 private var dismissListener: PopupWindow.OnDismissListener? = null 50 @GravityFlags private var dropDownGravity: Int = NO_GRAVITY 51 52 init { 53 setBackgroundDrawable(dialogBackground) 54 55 inputMethodMode = INPUT_METHOD_NOT_NEEDED 56 isModal = true 57 58 // dismiss method isn't called when popup is hidden by outside touch. So we need to 59 // override a listener to remove a dimming foreground <lambda>null60 super.setOnDismissListener { 61 anchorView?.rootView?.foreground = null 62 dismissListener?.onDismiss() 63 } 64 } 65 shownull66 override fun show() { 67 // need to call show() first in order to construct the listView 68 super.show() 69 updateWidth() 70 anchorView?.let { 71 positionPopup(it) 72 it.rootView.foreground = dimDrawable 73 } 74 with(listView!!) { 75 clipToOutline = true 76 background = dialogBackground 77 dividerHeight = listDividerHeight 78 } 79 // actual show takes into account updated ListView specs 80 super.show() 81 } 82 setDropDownGravitynull83 override fun setDropDownGravity(@GravityFlags gravity: Int) { 84 super.setDropDownGravity(gravity) 85 dropDownGravity = gravity 86 } 87 setOnDismissListenernull88 override fun setOnDismissListener(listener: PopupWindow.OnDismissListener?) { 89 dismissListener = listener 90 } 91 updateWidthnull92 private fun updateWidth() { 93 val paddedWidth = resources.displayMetrics.widthPixels - 2 * horizontalMargin 94 val maxWidth = maxWidth.coerceAtMost(paddedWidth) 95 when (width) { 96 ViewGroup.LayoutParams.MATCH_PARENT -> { 97 width = maxWidth 98 } 99 ViewGroup.LayoutParams.WRAP_CONTENT -> { 100 width = listView!!.measureDesiredWidth(maxWidth).coerceAtMost(maxWidth) 101 } 102 } 103 } 104 positionPopupnull105 private fun positionPopup(anchorView: View) { 106 when (dropDownGravity) { 107 NO_GRAVITY -> { 108 horizontalOffset = (-width + anchorView.width) / 2 109 if (anchorView.layoutDirection == View.LAYOUT_DIRECTION_RTL) { 110 horizontalOffset = -horizontalOffset 111 } 112 } 113 END, 114 START -> { 115 horizontalOffset = 0 116 } 117 } 118 verticalOffset = -anchorView.height / 2 119 } 120 ListViewnull121 private fun ListView.measureDesiredWidth(maxWidth: Int): Int { 122 var maxItemWidth = 0 123 repeat(adapter.count) { 124 val view = adapter.getView(it, null, listView) 125 view.measure( 126 MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST), 127 MeasureSpec.UNSPECIFIED 128 ) 129 maxItemWidth = max(maxItemWidth, view.measuredWidth) 130 } 131 return maxItemWidth 132 } 133 } 134