Merge "[Chipbar] Always tint the start icon correctly." into tm-qpr-dev am: 80821636d9

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20652875

Change-Id: Ibebf161374c1305c902e8488d2a6cf926a10351f
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
TreeHugger Robot
2022-12-08 23:18:32 +00:00
committed by Automerger Merge Worker
9 changed files with 190 additions and 82 deletions

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2022 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.common.shared.model
import androidx.annotation.AttrRes
/** Models an icon with a specific tint. */
data class TintedIcon(
val icon: Icon,
@AttrRes val tintAttr: Int?,
)

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2022 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.common.ui.binder
import android.widget.ImageView
import com.android.settingslib.Utils
import com.android.systemui.common.shared.model.TintedIcon
object TintedIconViewBinder {
/**
* Binds the given tinted icon to the view.
*
* [TintedIcon.tintAttr] will always be applied, meaning that if it is null, then the tint
* *will* be reset to null.
*/
fun bind(
tintedIcon: TintedIcon,
view: ImageView,
) {
IconViewBinder.bind(tintedIcon.icon, view)
view.imageTintList =
if (tintedIcon.tintAttr != null) {
Utils.getColorAttr(view.context, tintedIcon.tintAttr)
} else {
null
}
}
}

View File

@@ -19,10 +19,12 @@ package com.android.systemui.media.taptotransfer.common
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import com.android.settingslib.Utils import androidx.annotation.AttrRes
import androidx.annotation.DrawableRes
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.TintedIcon
/** Utility methods for media tap-to-transfer. */ /** Utility methods for media tap-to-transfer. */
class MediaTttUtils { class MediaTttUtils {
@@ -33,23 +35,6 @@ class MediaTttUtils {
const val WAKE_REASON_SENDER = "MEDIA_TRANSFER_ACTIVATED_SENDER" const val WAKE_REASON_SENDER = "MEDIA_TRANSFER_ACTIVATED_SENDER"
const val WAKE_REASON_RECEIVER = "MEDIA_TRANSFER_ACTIVATED_RECEIVER" const val WAKE_REASON_RECEIVER = "MEDIA_TRANSFER_ACTIVATED_RECEIVER"
/**
* Returns the information needed to display the icon in [Icon] form.
*
* See [getIconInfoFromPackageName].
*/
fun getIconFromPackageName(
context: Context,
appPackageName: String?,
logger: MediaTttLogger,
): Icon {
val iconInfo = getIconInfoFromPackageName(context, appPackageName, logger)
return Icon.Loaded(
iconInfo.drawable,
ContentDescription.Loaded(iconInfo.contentDescription)
)
}
/** /**
* Returns the information needed to display the icon. * Returns the information needed to display the icon.
* *
@@ -65,18 +50,22 @@ class MediaTttUtils {
logger: MediaTttLogger logger: MediaTttLogger
): IconInfo { ): IconInfo {
if (appPackageName != null) { if (appPackageName != null) {
val packageManager = context.packageManager
try { try {
val contentDescription = val contentDescription =
context.packageManager ContentDescription.Loaded(
packageManager
.getApplicationInfo( .getApplicationInfo(
appPackageName, appPackageName,
PackageManager.ApplicationInfoFlags.of(0) PackageManager.ApplicationInfoFlags.of(0)
) )
.loadLabel(context.packageManager) .loadLabel(packageManager)
.toString() .toString()
)
return IconInfo( return IconInfo(
contentDescription, contentDescription,
drawable = context.packageManager.getApplicationIcon(appPackageName), MediaTttIcon.Loaded(packageManager.getApplicationIcon(appPackageName)),
tintAttr = null,
isAppIcon = true isAppIcon = true
) )
} catch (e: PackageManager.NameNotFoundException) { } catch (e: PackageManager.NameNotFoundException) {
@@ -84,25 +73,41 @@ class MediaTttUtils {
} }
} }
return IconInfo( return IconInfo(
contentDescription = ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name),
context.getString(R.string.media_output_dialog_unknown_launch_app_name), MediaTttIcon.Resource(R.drawable.ic_cast),
drawable = tintAttr = android.R.attr.textColorPrimary,
context.resources.getDrawable(R.drawable.ic_cast).apply {
this.setTint(
Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
)
},
isAppIcon = false isAppIcon = false
) )
} }
} }
} }
/** Stores all the information for an icon shown with media TTT. */
data class IconInfo( data class IconInfo(
val contentDescription: String, val contentDescription: ContentDescription,
val drawable: Drawable, val icon: MediaTttIcon,
@AttrRes val tintAttr: Int?,
/** /**
* True if [drawable] is the app's icon, and false if [drawable] is some generic default icon. * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon.
*/ */
val isAppIcon: Boolean val isAppIcon: Boolean
) ) {
/** Converts this into a [TintedIcon]. */
fun toTintedIcon(): TintedIcon {
val iconOutput =
when (icon) {
is MediaTttIcon.Loaded -> Icon.Loaded(icon.drawable, contentDescription)
is MediaTttIcon.Resource -> Icon.Resource(icon.res, contentDescription)
}
return TintedIcon(iconOutput, tintAttr)
}
}
/**
* Mimics [com.android.systemui.common.shared.model.Icon] but without the content description, since
* the content description may need to be overridden.
*/
sealed interface MediaTttIcon {
data class Loaded(val drawable: Drawable) : MediaTttIcon
data class Resource(@DrawableRes val res: Int) : MediaTttIcon
}

View File

@@ -33,9 +33,12 @@ import android.view.accessibility.AccessibilityManager
import com.android.internal.widget.CachingIconView import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils import com.android.settingslib.Utils
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.ui.binder.TintedIconViewBinder
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttIcon
import com.android.systemui.media.taptotransfer.common.MediaTttLogger import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.CommandQueue
@@ -161,11 +164,23 @@ open class MediaTttChipControllerReceiver @Inject constructor(
} }
override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) { override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
val iconInfo = MediaTttUtils.getIconInfoFromPackageName( var iconInfo = MediaTttUtils.getIconInfoFromPackageName(
context, newInfo.routeInfo.clientPackageName, logger context, newInfo.routeInfo.clientPackageName, logger
) )
val iconDrawable = newInfo.appIconDrawableOverride ?: iconInfo.drawable
val iconContentDescription = newInfo.appNameOverride ?: iconInfo.contentDescription if (newInfo.appNameOverride != null) {
iconInfo = iconInfo.copy(
contentDescription = ContentDescription.Loaded(newInfo.appNameOverride.toString())
)
}
if (newInfo.appIconDrawableOverride != null) {
iconInfo = iconInfo.copy(
icon = MediaTttIcon.Loaded(newInfo.appIconDrawableOverride),
isAppIcon = true,
)
}
val iconPadding = val iconPadding =
if (iconInfo.isAppIcon) { if (iconInfo.isAppIcon) {
0 0
@@ -175,8 +190,7 @@ open class MediaTttChipControllerReceiver @Inject constructor(
val iconView = currentView.getAppIconView() val iconView = currentView.getAppIconView()
iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding) iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
iconView.setImageDrawable(iconDrawable) TintedIconViewBinder.bind(iconInfo.toTintedIcon(), iconView)
iconView.contentDescription = iconContentDescription
} }
override fun animateViewIn(view: ViewGroup) { override fun animateViewIn(view: ViewGroup) {

View File

@@ -153,7 +153,9 @@ constructor(
return ChipbarInfo( return ChipbarInfo(
// Display the app's icon as the start icon // Display the app's icon as the start icon
startIcon = MediaTttUtils.getIconFromPackageName(context, packageName, logger), startIcon =
MediaTttUtils.getIconInfoFromPackageName(context, packageName, logger)
.toTintedIcon(),
text = chipStateSender.getChipTextString(context, otherDeviceName), text = chipStateSender.getChipTextString(context, otherDeviceName),
endItem = endItem =
when (chipStateSender.endItem) { when (chipStateSender.endItem) {

View File

@@ -34,8 +34,8 @@ import com.android.systemui.animation.ViewHierarchyAnimator
import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text.Companion.loadText import com.android.systemui.common.shared.model.Text.Companion.loadText
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.common.ui.binder.TextViewBinder import com.android.systemui.common.ui.binder.TextViewBinder
import com.android.systemui.common.ui.binder.TintedIconViewBinder
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager
@@ -121,7 +121,7 @@ open class ChipbarCoordinator @Inject constructor(
// ---- Start icon ---- // ---- Start icon ----
val iconView = currentView.requireViewById<CachingIconView>(R.id.start_icon) val iconView = currentView.requireViewById<CachingIconView>(R.id.start_icon)
IconViewBinder.bind(newInfo.startIcon, iconView) TintedIconViewBinder.bind(newInfo.startIcon, iconView)
// ---- Text ---- // ---- Text ----
val textView = currentView.requireViewById<TextView>(R.id.text) val textView = currentView.requireViewById<TextView>(R.id.text)
@@ -159,7 +159,7 @@ open class ChipbarCoordinator @Inject constructor(
currentView.requireViewById<ViewGroup>( currentView.requireViewById<ViewGroup>(
R.id.chipbar_inner R.id.chipbar_inner
).contentDescription = ).contentDescription =
"${newInfo.startIcon.contentDescription.loadContentDescription(context)} " + "${newInfo.startIcon.icon.contentDescription.loadContentDescription(context)} " +
"${newInfo.text.loadText(context)}" "${newInfo.text.loadText(context)}"
// ---- Haptics ---- // ---- Haptics ----

View File

@@ -18,8 +18,9 @@ package com.android.systemui.temporarydisplay.chipbar
import android.os.VibrationEffect import android.os.VibrationEffect
import android.view.View import android.view.View
import com.android.systemui.common.shared.model.Icon import androidx.annotation.AttrRes
import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.temporarydisplay.TemporaryViewInfo import com.android.systemui.temporarydisplay.TemporaryViewInfo
/** /**
@@ -33,7 +34,7 @@ import com.android.systemui.temporarydisplay.TemporaryViewInfo
* @property vibrationEffect an optional vibration effect when the chipbar is displayed * @property vibrationEffect an optional vibration effect when the chipbar is displayed
*/ */
data class ChipbarInfo( data class ChipbarInfo(
val startIcon: Icon, val startIcon: TintedIcon,
val text: Text, val text: Text,
val endItem: ChipbarEndItem?, val endItem: ChipbarEndItem?,
val vibrationEffect: VibrationEffect? = null, val vibrationEffect: VibrationEffect? = null,
@@ -41,7 +42,11 @@ data class ChipbarInfo(
override val wakeReason: String, override val wakeReason: String,
override val timeoutMs: Int, override val timeoutMs: Int,
override val id: String, override val id: String,
) : TemporaryViewInfo() ) : TemporaryViewInfo() {
companion object {
@AttrRes const val DEFAULT_ICON_TINT_ATTR = android.R.attr.textColorPrimary
}
}
/** The possible items to display at the end of the chipbar. */ /** The possible items to display at the end of the chipbar. */
sealed class ChipbarEndItem { sealed class ChipbarEndItem {

View File

@@ -64,42 +64,15 @@ class MediaTttUtilsTest : SysuiTestCase() {
context.setMockPackageManager(packageManager) context.setMockPackageManager(packageManager)
} }
@Test
fun getIconFromPackageName_nullPackageName_returnsDefault() {
val icon = MediaTttUtils.getIconFromPackageName(context, appPackageName = null, logger)
val expectedDesc =
ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name)
.loadContentDescription(context)
assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc)
}
@Test
fun getIconFromPackageName_invalidPackageName_returnsDefault() {
val icon = MediaTttUtils.getIconFromPackageName(context, "fakePackageName", logger)
val expectedDesc =
ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name)
.loadContentDescription(context)
assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc)
}
@Test
fun getIconFromPackageName_validPackageName_returnsAppInfo() {
val icon = MediaTttUtils.getIconFromPackageName(context, PACKAGE_NAME, logger)
assertThat(icon)
.isEqualTo(Icon.Loaded(appIconFromPackageName, ContentDescription.Loaded(APP_NAME)))
}
@Test @Test
fun getIconInfoFromPackageName_nullPackageName_returnsDefault() { fun getIconInfoFromPackageName_nullPackageName_returnsDefault() {
val iconInfo = val iconInfo =
MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger) MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger)
assertThat(iconInfo.isAppIcon).isFalse() assertThat(iconInfo.isAppIcon).isFalse()
assertThat(iconInfo.contentDescription) assertThat(iconInfo.contentDescription.loadContentDescription(context))
.isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name)) .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name))
assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast))
} }
@Test @Test
@@ -107,8 +80,9 @@ class MediaTttUtilsTest : SysuiTestCase() {
val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName", logger) val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName", logger)
assertThat(iconInfo.isAppIcon).isFalse() assertThat(iconInfo.isAppIcon).isFalse()
assertThat(iconInfo.contentDescription) assertThat(iconInfo.contentDescription.loadContentDescription(context))
.isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name)) .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name))
assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast))
} }
@Test @Test
@@ -116,8 +90,48 @@ class MediaTttUtilsTest : SysuiTestCase() {
val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, logger) val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, logger)
assertThat(iconInfo.isAppIcon).isTrue() assertThat(iconInfo.isAppIcon).isTrue()
assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName) assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Loaded(appIconFromPackageName))
assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME) assertThat(iconInfo.contentDescription.loadContentDescription(context)).isEqualTo(APP_NAME)
}
@Test
fun iconInfo_toTintedIcon_loaded() {
val contentDescription = ContentDescription.Loaded("test")
val drawable = context.getDrawable(R.drawable.ic_cake)!!
val tintAttr = android.R.attr.textColorTertiary
val iconInfo =
IconInfo(
contentDescription,
MediaTttIcon.Loaded(drawable),
tintAttr,
isAppIcon = false,
)
val tinted = iconInfo.toTintedIcon()
assertThat(tinted.icon).isEqualTo(Icon.Loaded(drawable, contentDescription))
assertThat(tinted.tintAttr).isEqualTo(tintAttr)
}
@Test
fun iconInfo_toTintedIcon_resource() {
val contentDescription = ContentDescription.Loaded("test")
val drawableRes = R.drawable.ic_cake
val tintAttr = android.R.attr.textColorTertiary
val iconInfo =
IconInfo(
contentDescription,
MediaTttIcon.Resource(drawableRes),
tintAttr,
isAppIcon = false
)
val tinted = iconInfo.toTintedIcon()
assertThat(tinted.icon).isEqualTo(Icon.Resource(drawableRes, contentDescription))
assertThat(tinted.tintAttr).isEqualTo(tintAttr)
} }
} }

View File

@@ -35,6 +35,7 @@ import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController
@@ -370,7 +371,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
vibrationEffect: VibrationEffect? = null, vibrationEffect: VibrationEffect? = null,
): ChipbarInfo { ): ChipbarInfo {
return ChipbarInfo( return ChipbarInfo(
startIcon, TintedIcon(startIcon, tintAttr = null),
text, text,
endItem, endItem,
vibrationEffect, vibrationEffect,