Merge "Updates home controls overflow menu visuals" into udc-dev

This commit is contained in:
Anton Potapov
2023-04-13 13:33:21 +00:00
committed by Android (Google) Code Review
8 changed files with 178 additions and 46 deletions

View File

@@ -13,13 +13,17 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/controls_more_item_text"
style="@style/Control.MenuItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_height="@dimen/control_menu_item_height"
android:layout_gravity="center_vertical"
android:background="@drawable/controls_popup_item_background"
android:paddingStart="@dimen/control_menu_horizontal_padding"
android:paddingEnd="@dimen/control_menu_horizontal_padding"
android:textDirection="locale"/>
android:textDirection="locale"
android:textSize="@dimen/control_item_text_size"
tools:fontFamily="@null"
tools:text="@tools:sample/lorem/random" />

View File

@@ -16,18 +16,18 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="@dimen/control_popup_item_height"
android:layout_height="@dimen/control_apps_popup_item_height"
android:background="@drawable/controls_popup_item_background"
android:gravity="center_vertical|start"
android:orientation="horizontal"
android:paddingStart="@dimen/control_popup_item_padding"
android:paddingEnd="@dimen/control_popup_item_padding">
android:paddingStart="@dimen/control_menu_horizontal_padding"
android:paddingEnd="@dimen/control_menu_horizontal_padding">
<ImageView
android:id="@+id/app_icon"
android:layout_width="@dimen/controls_header_app_icon_size"
android:layout_height="@dimen/controls_header_app_icon_size"
android:layout_marginEnd="@dimen/control_popup_item_padding"
android:layout_marginEnd="@dimen/control_menu_horizontal_padding"
android:contentDescription="@null"
tools:src="@drawable/ic_android" />

View File

@@ -1199,19 +1199,17 @@
<dimen name="controls_top_margin">48dp</dimen>
<dimen name="controls_content_margin_horizontal">0dp</dimen>
<dimen name="control_header_text_size">24sp</dimen>
<dimen name="control_item_text_size">16sp</dimen>
<dimen name="control_item_text_size">14sp</dimen>
<dimen name="control_menu_item_text_size">16sp</dimen>
<dimen name="control_menu_item_min_height">56dp</dimen>
<dimen name="control_menu_item_height">54dp</dimen>
<dimen name="control_menu_vertical_padding">12dp</dimen>
<dimen name="control_menu_horizontal_padding">16dp</dimen>
<dimen name="control_popup_item_corner_radius">4dp</dimen>
<dimen name="control_popup_item_height">56dp</dimen>
<dimen name="control_popup_item_padding">16dp</dimen>
<dimen name="control_popup_items_divider_height">1dp</dimen>
<dimen name="control_menu_horizontal_padding">@dimen/notification_side_paddings</dimen>
<dimen name="control_apps_popup_item_height">56dp</dimen>
<dimen name="control_popup_item_corner_radius">@dimen/notification_corner_radius_small</dimen>
<dimen name="control_popup_items_divider_height">@dimen/controls_app_divider_height</dimen>
<dimen name="control_popup_max_width">380dp</dimen>
<dimen name="control_popup_corner_radius">28dp</dimen>
<dimen name="control_popup_corner_radius">@dimen/notification_corner_radius</dimen>
<dimen name="control_popup_horizontal_margin">16dp</dimen>
<dimen name="control_spinner_padding_vertical">24dp</dimen>
<dimen name="control_spinner_padding_horizontal">20dp</dimen>
<dimen name="control_text_size">14sp</dimen>
<dimen name="control_icon_size">24dp</dimen>

View File

@@ -895,7 +895,7 @@
<item name="android:textColor">@color/control_primary_text</item>
<item name="android:singleLine">true</item>
<item name="android:gravity">center_vertical</item>
<item name="android:minHeight">@dimen/control_menu_item_min_height</item>
<item name="android:minHeight">@dimen/control_menu_item_height</item>
</style>
<style name="Control.Spinner">

View File

@@ -20,11 +20,18 @@ import android.content.Context
import android.content.res.Resources
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.view.Gravity
import android.view.Gravity.END
import android.view.Gravity.GravityFlags
import android.view.Gravity.NO_GRAVITY
import android.view.Gravity.START
import android.view.View
import android.view.View.MeasureSpec
import android.view.ViewGroup
import android.widget.ListPopupWindow
import android.widget.ListView
import android.widget.PopupWindow
import com.android.systemui.R
import kotlin.math.max
class ControlsPopupMenu(context: Context) : ListPopupWindow(context) {
@@ -40,13 +47,13 @@ class ControlsPopupMenu(context: Context) : ListPopupWindow(context) {
private val dimDrawable: Drawable = ColorDrawable(resources.getColor(R.color.control_popup_dim))
private var dismissListener: PopupWindow.OnDismissListener? = null
@GravityFlags private var dropDownGravity: Int = NO_GRAVITY
init {
setBackgroundDrawable(dialogBackground)
inputMethodMode = INPUT_METHOD_NOT_NEEDED
isModal = true
setDropDownGravity(Gravity.START)
// dismiss method isn't called when popup is hidden by outside touch. So we need to
// override a listener to remove a dimming foreground
@@ -59,30 +66,68 @@ class ControlsPopupMenu(context: Context) : ListPopupWindow(context) {
override fun show() {
// need to call show() first in order to construct the listView
super.show()
val paddedWidth = resources.displayMetrics.widthPixels - 2 * horizontalMargin
width = maxWidth.coerceAtMost(paddedWidth)
updateWidth()
anchorView?.let {
horizontalOffset = -width / 2 + it.width / 2
verticalOffset = -it.height / 2
if (it.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
horizontalOffset = -horizontalOffset
}
positionPopup(it)
it.rootView.foreground = dimDrawable
}
with(listView!!) {
clipToOutline = true
background = dialogBackground
dividerHeight = listDividerHeight
}
// actual show takes into account updated ListView specs
super.show()
}
override fun setDropDownGravity(@GravityFlags gravity: Int) {
super.setDropDownGravity(gravity)
dropDownGravity = gravity
}
override fun setOnDismissListener(listener: PopupWindow.OnDismissListener?) {
dismissListener = listener
}
private fun updateWidth() {
val paddedWidth = resources.displayMetrics.widthPixels - 2 * horizontalMargin
val maxWidth = maxWidth.coerceAtMost(paddedWidth)
when (width) {
ViewGroup.LayoutParams.MATCH_PARENT -> {
width = maxWidth
}
ViewGroup.LayoutParams.WRAP_CONTENT -> {
width = listView!!.measureDesiredWidth(maxWidth).coerceAtMost(maxWidth)
}
}
}
private fun positionPopup(anchorView: View) {
when (dropDownGravity) {
NO_GRAVITY -> {
horizontalOffset = (-width + anchorView.width) / 2
if (anchorView.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
horizontalOffset = -horizontalOffset
}
}
END,
START -> {
horizontalOffset = 0
}
}
verticalOffset = -anchorView.height / 2
}
private fun ListView.measureDesiredWidth(maxWidth: Int): Int {
var maxItemWidth = 0
repeat(adapter.count) {
val view = adapter.getView(it, null, listView)
view.measure(
MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
MeasureSpec.UNSPECIFIED
)
maxItemWidth = max(maxItemWidth, view.measuredWidth)
}
return maxItemWidth
}
}

View File

@@ -34,6 +34,7 @@ import android.service.controls.Control
import android.service.controls.ControlsProviderService
import android.util.Log
import android.view.ContextThemeWrapper
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -72,7 +73,6 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -540,12 +540,12 @@ class ControlsUiControllerImpl @Inject constructor (
val anchor = parent.requireViewById<ImageView>(R.id.controls_more)
anchor.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View) {
popup = GlobalActionsPopupMenu(
popupThemedContext,
false /* isDropDownMode */
).apply {
setAnchorView(anchor)
popup = ControlsPopupMenu(popupThemedContext).apply {
width = ViewGroup.LayoutParams.WRAP_CONTENT
anchorView = anchor
setDropDownGravity(Gravity.END)
setAdapter(adapter)
setOnItemClickListener(object : AdapterView.OnItemClickListener {
override fun onItemClick(
parent: AdapterView<*>,
@@ -618,7 +618,8 @@ class ControlsUiControllerImpl @Inject constructor (
anchor.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View) {
popup = ControlsPopupMenu(popupThemedContext).apply {
setAnchorView(anchor)
anchorView = anchor
width = ViewGroup.LayoutParams.MATCH_PARENT
setAdapter(adapter)
setOnItemClickListener(object : AdapterView.OnItemClickListener {

View File

@@ -22,6 +22,7 @@ import android.graphics.drawable.ShapeDrawable
import android.testing.AndroidTestingRunner
import android.util.DisplayMetrics
import android.view.View
import android.view.ViewGroup
import android.widget.PopupWindow.OnDismissListener
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.filters.SmallTest
@@ -29,13 +30,17 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.activity.EmptyTestActivity
import com.android.systemui.util.mockito.whenever
import com.android.systemui.widget.FakeListAdapter
import com.android.systemui.widget.FakeListAdapter.FakeListAdapterItem
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -52,10 +57,16 @@ open class ControlsPopupMenuTest : SysuiTestCase() {
@Rule @JvmField val activityScenarioRule = ActivityScenarioRule(EmptyTestActivity::class.java)
private val testDisplayMetrics: DisplayMetrics = DisplayMetrics()
private val testDisplayMetrics = DisplayMetrics()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
}
@Test
fun testDismissListenerWorks() = testPopup { popupMenu ->
fun testDismissListenerWorks() = testPopup { activity, popupMenu ->
popupMenu.setAdapter(FakeListAdapter())
val listener = mock(OnDismissListener::class.java)
popupMenu.setOnDismissListener(listener)
popupMenu.show()
@@ -66,7 +77,9 @@ open class ControlsPopupMenuTest : SysuiTestCase() {
}
@Test
fun testPopupDoesntExceedMaxWidth() = testPopup { popupMenu ->
fun testPopupDoesntExceedMaxWidth() = testPopup { activity, popupMenu ->
popupMenu.setAdapter(FakeListAdapter())
popupMenu.width = ViewGroup.LayoutParams.MATCH_PARENT
testDisplayMetrics.widthPixels = DISPLAY_WIDTH_WIDE
popupMenu.show()
@@ -75,7 +88,9 @@ open class ControlsPopupMenuTest : SysuiTestCase() {
}
@Test
fun testPopupMarginsWidthLessMax() = testPopup { popupMenu ->
fun testPopupMarginsWidthLessMax() = testPopup { activity, popupMenu ->
popupMenu.setAdapter(FakeListAdapter())
popupMenu.width = ViewGroup.LayoutParams.MATCH_PARENT
testDisplayMetrics.widthPixels = DISPLAY_WIDTH_NARROW
popupMenu.show()
@@ -83,10 +98,32 @@ open class ControlsPopupMenuTest : SysuiTestCase() {
assertThat(popupMenu.width).isEqualTo(DISPLAY_WIDTH_NARROW - 2 * HORIZONTAL_MARGIN)
}
private fun testPopup(test: (popup: ControlsPopupMenu) -> Unit) {
@Test
fun testWrapContentDoesntExceedMax() = testPopup { activity, popupMenu ->
popupMenu.setAdapter(
FakeListAdapter(
listOf(
FakeListAdapterItem({ _, _, _ ->
View(activity).apply { minimumWidth = MAX_WIDTH + 1 }
})
)
)
)
popupMenu.width = ViewGroup.LayoutParams.WRAP_CONTENT
testDisplayMetrics.widthPixels = DISPLAY_WIDTH_NARROW
popupMenu.show()
assertThat(popupMenu.width).isEqualTo(DISPLAY_WIDTH_NARROW - 2 * HORIZONTAL_MARGIN)
}
private fun testPopup(test: (activity: Activity, popup: ControlsPopupMenu) -> Unit) {
activityScenarioRule.scenario.onActivity { activity ->
val testActivity = setupActivity(activity)
test(ControlsPopupMenu(testActivity).apply { anchorView = View(testActivity) })
test(
testActivity,
ControlsPopupMenu(testActivity).apply { anchorView = View(testActivity) }
)
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.widget
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
class FakeListAdapter(private var items: List<FakeListAdapterItem> = emptyList()) : BaseAdapter() {
fun setItems(items: List<FakeListAdapterItem>) {
this.items = items
notifyDataSetChanged()
}
override fun getCount(): Int = items.size
override fun getItem(position: Int): Any = items[position].data
override fun getItemId(position: Int): Long = items[position].id
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View =
items[position].view(position, convertView, parent)
class FakeListAdapterItem(
/** Result returned in [Adapter#getView] */
val view: (position: Int, convertView: View?, parent: ViewGroup?) -> View,
/** Returned in [Adapter#getItemId] */
val id: Long = 0,
/** Returned in [Adapter#getItem] */
val data: Any = Unit,
)
}