diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt new file mode 100644 index 0000000000000..5dabbbb81701d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt @@ -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?, +) diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt new file mode 100644 index 0000000000000..dea8cfda80c34 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt @@ -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 + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt index 769494a588425..009595a6da8b7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt @@ -19,10 +19,12 @@ package com.android.systemui.media.taptotransfer.common import android.content.Context import android.content.pm.PackageManager 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.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.TintedIcon /** Utility methods for media tap-to-transfer. */ class MediaTttUtils { @@ -33,23 +35,6 @@ class MediaTttUtils { const val WAKE_REASON_SENDER = "MEDIA_TRANSFER_ACTIVATED_SENDER" 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. * @@ -65,18 +50,22 @@ class MediaTttUtils { logger: MediaTttLogger ): IconInfo { if (appPackageName != null) { + val packageManager = context.packageManager try { val contentDescription = - context.packageManager - .getApplicationInfo( - appPackageName, - PackageManager.ApplicationInfoFlags.of(0) - ) - .loadLabel(context.packageManager) - .toString() + ContentDescription.Loaded( + packageManager + .getApplicationInfo( + appPackageName, + PackageManager.ApplicationInfoFlags.of(0) + ) + .loadLabel(packageManager) + .toString() + ) return IconInfo( contentDescription, - drawable = context.packageManager.getApplicationIcon(appPackageName), + MediaTttIcon.Loaded(packageManager.getApplicationIcon(appPackageName)), + tintAttr = null, isAppIcon = true ) } catch (e: PackageManager.NameNotFoundException) { @@ -84,25 +73,41 @@ class MediaTttUtils { } } return IconInfo( - contentDescription = - context.getString(R.string.media_output_dialog_unknown_launch_app_name), - drawable = - context.resources.getDrawable(R.drawable.ic_cast).apply { - this.setTint( - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) - ) - }, + ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name), + MediaTttIcon.Resource(R.drawable.ic_cast), + tintAttr = android.R.attr.textColorPrimary, isAppIcon = false ) } } } +/** Stores all the information for an icon shown with media TTT. */ data class IconInfo( - val contentDescription: String, - val drawable: Drawable, + val contentDescription: ContentDescription, + val icon: MediaTttIcon, + @AttrRes val tintAttr: Int?, /** * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon. */ 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 +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index cc5e256c09563..1c3a53cbf815d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -33,9 +33,12 @@ import android.view.accessibility.AccessibilityManager import com.android.internal.widget.CachingIconView import com.android.settingslib.Utils 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.qualifiers.Main 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.MediaTttUtils import com.android.systemui.statusbar.CommandQueue @@ -161,11 +164,23 @@ open class MediaTttChipControllerReceiver @Inject constructor( } override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) { - val iconInfo = MediaTttUtils.getIconInfoFromPackageName( + var iconInfo = MediaTttUtils.getIconInfoFromPackageName( 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 = if (iconInfo.isAppIcon) { 0 @@ -175,8 +190,7 @@ open class MediaTttChipControllerReceiver @Inject constructor( val iconView = currentView.getAppIconView() iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding) - iconView.setImageDrawable(iconDrawable) - iconView.contentDescription = iconContentDescription + TintedIconViewBinder.bind(iconInfo.toTintedIcon(), iconView) } override fun animateViewIn(view: ViewGroup) { diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt index bb7bc6fff99fa..f401ab9a4b1cc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt @@ -138,7 +138,9 @@ constructor( return ChipbarInfo( // 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), endItem = when (chipStateSender.endItem) { diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt index fb17b693e17e1..3b8b1e3a5933f 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt @@ -34,8 +34,8 @@ import com.android.systemui.animation.ViewHierarchyAnimator import com.android.systemui.classifier.FalsingCollector 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.ui.binder.IconViewBinder 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.qualifiers.Main import com.android.systemui.plugins.FalsingManager @@ -121,7 +121,7 @@ open class ChipbarCoordinator @Inject constructor( // ---- Start icon ---- val iconView = currentView.requireViewById(R.id.start_icon) - IconViewBinder.bind(newInfo.startIcon, iconView) + TintedIconViewBinder.bind(newInfo.startIcon, iconView) // ---- Text ---- val textView = currentView.requireViewById(R.id.text) @@ -159,7 +159,7 @@ open class ChipbarCoordinator @Inject constructor( currentView.requireViewById( R.id.chipbar_inner ).contentDescription = - "${newInfo.startIcon.contentDescription.loadContentDescription(context)} " + + "${newInfo.startIcon.icon.contentDescription.loadContentDescription(context)} " + "${newInfo.text.loadText(context)}" // ---- Haptics ---- diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt index b92e0ec0428f2..a3eef8032b3b4 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt @@ -18,8 +18,9 @@ package com.android.systemui.temporarydisplay.chipbar import android.os.VibrationEffect 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.TintedIcon 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 */ data class ChipbarInfo( - val startIcon: Icon, + val startIcon: TintedIcon, val text: Text, val endItem: ChipbarEndItem?, val vibrationEffect: VibrationEffect? = null, @@ -41,7 +42,11 @@ data class ChipbarInfo( override val wakeReason: String, override val timeoutMs: Int, 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. */ sealed class ChipbarEndItem { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt index 6a4c0f60466d4..cce3e369c0b8e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt @@ -64,42 +64,15 @@ class MediaTttUtilsTest : SysuiTestCase() { 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 fun getIconInfoFromPackageName_nullPackageName_returnsDefault() { val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger) assertThat(iconInfo.isAppIcon).isFalse() - assertThat(iconInfo.contentDescription) + assertThat(iconInfo.contentDescription.loadContentDescription(context)) .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name)) + assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast)) } @Test @@ -107,8 +80,9 @@ class MediaTttUtilsTest : SysuiTestCase() { val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName", logger) assertThat(iconInfo.isAppIcon).isFalse() - assertThat(iconInfo.contentDescription) + assertThat(iconInfo.contentDescription.loadContentDescription(context)) .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name)) + assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast)) } @Test @@ -116,8 +90,48 @@ class MediaTttUtilsTest : SysuiTestCase() { val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, logger) assertThat(iconInfo.isAppIcon).isTrue() - assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName) - assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME) + assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Loaded(appIconFromPackageName)) + 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) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt index 47c84ab480935..5343ad8340a79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt @@ -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.Icon 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.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.ConfigurationController @@ -370,7 +371,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() { vibrationEffect: VibrationEffect? = null, ): ChipbarInfo { return ChipbarInfo( - startIcon, + TintedIcon(startIcon, tintAttr = null), text, endItem, vibrationEffect,