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