Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fb01c569 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[Chipbar] Remove #setIcon from TemporaryViewDisplayController and put it

in MediaTttUtils instead.

Bug: 245610654
Test: Verified icon still displays correctly on sender and receiver
chips.
Test: media.taptotransfer tests
Test: temporarydislpay tests

Change-Id: Ibae88dbf0fcd171fbf0746cc05550e56f174b2dc
parent e221b76f
Loading
Loading
Loading
Loading
+105 −0
Original line number Diff line number Diff line
/*
 * 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.media.taptotransfer.common

import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.util.Log
import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R

/** Utility methods for media tap-to-transfer. */
class MediaTttUtils {
    companion object {
        /**
         * Returns the information needed to display the icon.
         *
         * The information will either contain app name and icon of the app playing media, or a
         * default name and icon if we can't find the app name/icon.
         *
         * @param appPackageName the package name of the app playing the media.
         */
        fun getIconInfoFromPackageName(context: Context, appPackageName: String?): IconInfo {
            if (appPackageName != null) {
                try {
                    val contentDescription =
                        context.packageManager
                            .getApplicationInfo(
                                appPackageName,
                                PackageManager.ApplicationInfoFlags.of(0)
                            )
                            .loadLabel(context.packageManager)
                            .toString()
                    return IconInfo(
                        contentDescription,
                        drawable = context.packageManager.getApplicationIcon(appPackageName),
                        isAppIcon = true
                    )
                } catch (e: PackageManager.NameNotFoundException) {
                    Log.w(TAG, "Cannot find package $appPackageName", e)
                }
            }
            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)
                        )
                    },
                isAppIcon = false
            )
        }

        /**
         * Sets an icon to be displayed by the given view.
         *
         * @param iconSize the size in pixels that the icon should be. If null, the size of
         * [appIconView] will not be adjusted.
         */
        fun setIcon(
            appIconView: CachingIconView,
            icon: Drawable,
            iconContentDescription: CharSequence,
            iconSize: Int? = null,
        ) {
            iconSize?.let { size ->
                val lp = appIconView.layoutParams
                lp.width = size
                lp.height = size
                appIconView.layoutParams = lp
            }

            appIconView.contentDescription = iconContentDescription
            appIconView.setImageDrawable(icon)
        }
    }
}

data class IconInfo(
    val contentDescription: String,
    val drawable: Drawable,
    /**
     * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon.
     */
    val isAppIcon: Boolean
)

private const val TAG = "MediaTtt"
+20 −15
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
@@ -137,13 +138,26 @@ class MediaTttChipControllerReceiver @Inject constructor(

    override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
        super.updateView(newInfo, currentView)
        val iconName = setIcon(
                currentView,
                newInfo.routeInfo.clientPackageName,
                newInfo.appIconDrawableOverride,
                newInfo.appNameOverride

        val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
            context, newInfo.routeInfo.clientPackageName
        )
        val iconDrawable = newInfo.appIconDrawableOverride ?: iconInfo.drawable
        val iconContentDescription = newInfo.appNameOverride ?: iconInfo.contentDescription
        val iconSize = context.resources.getDimensionPixelSize(
            if (iconInfo.isAppIcon) {
                R.dimen.media_ttt_icon_size_receiver
            } else {
                R.dimen.media_ttt_generic_icon_size_receiver
            }
        )

        MediaTttUtils.setIcon(
            currentView.requireViewById(R.id.app_icon),
            iconDrawable,
            iconContentDescription,
            iconSize,
        )
        currentView.contentDescription = iconName
    }

    override fun animateViewIn(view: ViewGroup) {
@@ -161,15 +175,6 @@ class MediaTttChipControllerReceiver @Inject constructor(
        startRipple(view.requireViewById(R.id.ripple))
    }

    override fun getIconSize(isAppIcon: Boolean): Int? =
        context.resources.getDimensionPixelSize(
            if (isAppIcon) {
                R.dimen.media_ttt_icon_size_receiver
            } else {
                R.dimen.media_ttt_generic_icon_size_receiver
            }
        )

    /** Returns the amount that the chip will be translated by in its intro animation. */
    private fun getTranslationAmount(): Int {
        return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
+10 −2
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import com.android.systemui.animation.ViewHierarchyAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryDisplayRemovalReason
@@ -118,7 +119,14 @@ class MediaTttChipControllerSender @Inject constructor(
        val chipState = newInfo.state

        // App icon
        val iconName = setIcon(currentView, newInfo.routeInfo.clientPackageName)
        val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
            context, newInfo.routeInfo.clientPackageName
        )
        MediaTttUtils.setIcon(
            currentView.requireViewById(R.id.app_icon),
            iconInfo.drawable,
            iconInfo.contentDescription
        )

        // Text
        val otherDeviceName = newInfo.routeInfo.name.toString()
@@ -144,7 +152,7 @@ class MediaTttChipControllerSender @Inject constructor(
        // For accessibility
        currentView.requireViewById<ViewGroup>(
                R.id.media_ttt_sender_chip_inner
        ).contentDescription = "$iconName $chipText"
        ).contentDescription = "${iconInfo.contentDescription} $chipText"
    }

    override fun animateViewIn(view: ViewGroup) {
+0 −73
Original line number Diff line number Diff line
@@ -19,12 +19,10 @@ package com.android.systemui.temporarydisplay
import android.annotation.LayoutRes
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
import android.os.PowerManager
import android.os.SystemClock
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.WindowManager
@@ -33,9 +31,6 @@ import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
import androidx.annotation.CallSuper
import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -192,79 +187,11 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo>(
     * appears.
     */
    open fun animateViewIn(view: ViewGroup) {}

    /**
     * Returns the size that the icon should be, or null if no size override is needed.
     */
    open fun getIconSize(isAppIcon: Boolean): Int? = null

    /**
     * An internal method to set the icon on the view.
     *
     * This is in the common superclass since both the sender and the receiver show an icon.
     *
     * @param appPackageName the package name of the app playing the media. Will be used to fetch
     *   the app icon and app name if overrides aren't provided.
     *
     * @return the content description of the icon.
     */
    internal fun setIcon(
        currentView: ViewGroup,
        appPackageName: String?,
        appIconDrawableOverride: Drawable? = null,
        appNameOverride: CharSequence? = null,
    ): CharSequence {
        val appIconView = currentView.requireViewById<CachingIconView>(R.id.app_icon)
        val iconInfo = getIconInfo(appPackageName)

        getIconSize(iconInfo.isAppIcon)?.let { size ->
            val lp = appIconView.layoutParams
            lp.width = size
            lp.height = size
            appIconView.layoutParams = lp
        }

        appIconView.contentDescription = appNameOverride ?: iconInfo.iconName
        appIconView.setImageDrawable(appIconDrawableOverride ?: iconInfo.icon)
        return appIconView.contentDescription
    }

    /**
     * Returns the information needed to display the icon.
     *
     * The information will either contain app name and icon of the app playing media, or a default
     * name and icon if we can't find the app name/icon.
     */
    private fun getIconInfo(appPackageName: String?): IconInfo {
        if (appPackageName != null) {
            try {
                return IconInfo(
                    iconName = context.packageManager.getApplicationInfo(
                        appPackageName, PackageManager.ApplicationInfoFlags.of(0)
                    ).loadLabel(context.packageManager).toString(),
                    icon = context.packageManager.getApplicationIcon(appPackageName),
                    isAppIcon = true
                )
            } catch (e: PackageManager.NameNotFoundException) {
                Log.w(TAG, "Cannot find package $appPackageName", e)
            }
        }
        return IconInfo(
            iconName = context.getString(R.string.media_output_dialog_unknown_launch_app_name),
            icon = context.resources.getDrawable(R.drawable.ic_cast).apply {
                this.setTint(
                    Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
                )
            },
            isAppIcon = false
        )
    }
}

// Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
// UpdateMediaTapToTransferReceiverDisplayTest
private const val WINDOW_TITLE = "Media Transfer Chip View"
private val TAG = TemporaryViewDisplayController::class.simpleName!!

object TemporaryDisplayRemovalReason {
    const val REASON_TIMEOUT = "TIMEOUT"
+136 −0
Original line number Diff line number Diff line
/*
 * 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.media.taptotransfer.common

import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.internal.widget.CachingIconView
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations

@SmallTest
class MediaTttUtilsTest : SysuiTestCase() {

    private lateinit var appIconFromPackageName: Drawable
    @Mock private lateinit var packageManager: PackageManager
    @Mock private lateinit var applicationInfo: ApplicationInfo

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        // Set up our package manager to give valid information for [PACKAGE_NAME] only
        appIconFromPackageName = context.getDrawable(R.drawable.ic_cake)!!
        whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(appIconFromPackageName)
        whenever(applicationInfo.loadLabel(packageManager)).thenReturn(APP_NAME)
        whenever(
                packageManager.getApplicationInfo(any(), any<PackageManager.ApplicationInfoFlags>())
            )
            .thenThrow(PackageManager.NameNotFoundException())
        whenever(
                packageManager.getApplicationInfo(
                    Mockito.eq(PACKAGE_NAME),
                    any<PackageManager.ApplicationInfoFlags>()
                )
            )
            .thenReturn(applicationInfo)
        context.setMockPackageManager(packageManager)
    }

    @Test
    fun getIconInfoFromPackageName_nullPackageName_returnsDefault() {
        val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null)

        assertThat(iconInfo.isAppIcon).isFalse()
        assertThat(iconInfo.contentDescription)
            .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name))
    }

    @Test
    fun getIconInfoFromPackageName_invalidPackageName_returnsDefault() {
        val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName")

        assertThat(iconInfo.isAppIcon).isFalse()
        assertThat(iconInfo.contentDescription)
            .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name))
    }

    @Test
    fun getIconInfoFromPackageName_validPackageName_returnsAppInfo() {
        val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME)

        assertThat(iconInfo.isAppIcon).isTrue()
        assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName)
        assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME)
    }

    @Test
    fun setIcon_viewHasIconAndContentDescription() {
        val view = CachingIconView(context)
        val icon = context.getDrawable(R.drawable.ic_celebration)!!
        val contentDescription = "Happy birthday!"

        MediaTttUtils.setIcon(view, icon, contentDescription)

        assertThat(view.drawable).isEqualTo(icon)
        assertThat(view.contentDescription).isEqualTo(contentDescription)
    }

    @Test
    fun setIcon_iconSizeNull_viewSizeDoesNotChange() {
        val view = CachingIconView(context)
        val size = 456
        view.layoutParams = FrameLayout.LayoutParams(size, size)

        MediaTttUtils.setIcon(view, context.getDrawable(R.drawable.ic_cake)!!, "desc")

        assertThat(view.layoutParams.width).isEqualTo(size)
        assertThat(view.layoutParams.height).isEqualTo(size)
    }

    @Test
    fun setIcon_iconSizeProvided_viewSizeUpdates() {
        val view = CachingIconView(context)
        val size = 456
        view.layoutParams = FrameLayout.LayoutParams(size, size)

        val newSize = 40
        MediaTttUtils.setIcon(
            view,
            context.getDrawable(R.drawable.ic_cake)!!,
            "desc",
            iconSize = newSize
        )

        assertThat(view.layoutParams.width).isEqualTo(newSize)
        assertThat(view.layoutParams.height).isEqualTo(newSize)
    }
}

private const val PACKAGE_NAME = "com.android.systemui"
private const val APP_NAME = "Fake App Name"
Loading