Updates home controls overflow menu visuals
Test: manual on foldable and phone. LTR and RTL layouts Fixes: 266407418 Change-Id: I225334d9aea5501bdd3088a91c01f2ca4fa85de5
This commit is contained in:
@@ -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" />
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -1198,19 +1198,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>
|
||||
|
||||
@@ -886,7 +886,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">
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user