diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt index aa10f7e2738f3..811d47ddaa29b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt @@ -28,28 +28,41 @@ class MediaTttLogger( private val deviceTypeTag: String, private val buffer: LogBuffer ){ + private val bufferTag = BASE_TAG + deviceTypeTag + /** Logs a change in the chip state for the given [mediaRouteId]. */ - fun logStateChange(stateName: String, mediaRouteId: String) { + fun logStateChange(stateName: String, mediaRouteId: String, packageName: String?) { buffer.log( - BASE_TAG + deviceTypeTag, + bufferTag, LogLevel.DEBUG, { str1 = stateName str2 = mediaRouteId + str3 = packageName }, - { "State changed to $str1 for ID=$str2" } + { "State changed to $str1 for ID=$str2 package=$str3" } ) } /** Logs that we removed the chip for the given [reason]. */ fun logChipRemoval(reason: String) { buffer.log( - BASE_TAG + deviceTypeTag, + bufferTag, LogLevel.DEBUG, { str1 = reason }, { "Chip removed due to $str1" } ) } + + /** Logs that we couldn't find information for [packageName]. */ + fun logPackageNotFound(packageName: String) { + buffer.log( + bufferTag, + LogLevel.DEBUG, + { str1 = packageName }, + { "Package $str1 could not be found" } + ) + } } private const val BASE_TAG = "MediaTtt" 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 new file mode 100644 index 0000000000000..b171241b72ccc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt @@ -0,0 +1,107 @@ +/* + * 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 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. + * @param logger the logger to use for any errors. + */ + fun getIconInfoFromPackageName( + context: Context, + appPackageName: String?, + logger: MediaTttLogger + ): 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) { + logger.logPackageNotFound(appPackageName) + } + } + 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 +) 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 5d6d683f93f6f..d218c2288420e 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 @@ -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 @@ -107,7 +108,7 @@ class MediaTttChipControllerReceiver @Inject constructor( ) { val chipState: ChipStateReceiver? = ChipStateReceiver.getReceiverStateFromId(displayState) val stateName = chipState?.name ?: "Invalid" - logger.logStateChange(stateName, routeInfo.id) + logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName) if (chipState == null) { Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState") @@ -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, logger + ) + 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) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index bde588c14fc83..4379d25406bfb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -34,8 +34,7 @@ import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS * @property stateInt the integer from [StatusBarManager] corresponding with this state. * @property stringResId the res ID of the string that should be displayed in the chip. Null if the * state should not have the chip be displayed. - * @property isMidTransfer true if the state represents that a transfer is currently ongoing. - * @property isTransferFailure true if the state represents that the transfer has failed. + * @property transferStatus the transfer status that the chip state represents. * @property timeout the amount of time this chip should display on the screen before it times out * and disappears. */ @@ -43,8 +42,7 @@ enum class ChipStateSender( @StatusBarManager.MediaTransferSenderState val stateInt: Int, val uiEvent: UiEventLogger.UiEventEnum, @StringRes val stringResId: Int?, - val isMidTransfer: Boolean = false, - val isTransferFailure: Boolean = false, + val transferStatus: TransferStatus, val timeout: Long = DEFAULT_TIMEOUT_MILLIS ) { /** @@ -56,6 +54,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST, R.string.media_move_closer_to_start_cast, + transferStatus = TransferStatus.NOT_STARTED, ), /** @@ -68,6 +67,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST, R.string.media_move_closer_to_end_cast, + transferStatus = TransferStatus.NOT_STARTED, ), /** @@ -78,7 +78,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED, R.string.media_transfer_playing_different_device, - isMidTransfer = true, + transferStatus = TransferStatus.IN_PROGRESS, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS ), @@ -90,7 +90,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED, R.string.media_transfer_playing_this_device, - isMidTransfer = true, + transferStatus = TransferStatus.IN_PROGRESS, timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS ), @@ -100,7 +100,8 @@ enum class ChipStateSender( TRANSFER_TO_RECEIVER_SUCCEEDED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED, - R.string.media_transfer_playing_different_device + R.string.media_transfer_playing_different_device, + transferStatus = TransferStatus.SUCCEEDED, ) { override fun undoClickListener( controllerSender: MediaTttChipControllerSender, @@ -135,7 +136,8 @@ enum class ChipStateSender( TRANSFER_TO_THIS_DEVICE_SUCCEEDED( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, - R.string.media_transfer_playing_this_device + R.string.media_transfer_playing_this_device, + transferStatus = TransferStatus.SUCCEEDED, ) { override fun undoClickListener( controllerSender: MediaTttChipControllerSender, @@ -169,7 +171,7 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED, R.string.media_transfer_failed, - isTransferFailure = true + transferStatus = TransferStatus.FAILED, ), /** A state representing that a transfer back to this device has failed. */ @@ -177,14 +179,15 @@ enum class ChipStateSender( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED, R.string.media_transfer_failed, - isTransferFailure = true + transferStatus = TransferStatus.FAILED, ), /** A state representing that this device is far away from any receiver device. */ FAR_FROM_RECEIVER( StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER, - stringResId = null + stringResId = null, + transferStatus = TransferStatus.TOO_FAR, ); /** diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 0c1ebd70c572f..7faea147d75b4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -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 @@ -94,7 +95,7 @@ class MediaTttChipControllerSender @Inject constructor( ) { val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState) val stateName = chipState?.name ?: "Invalid" - logger.logStateChange(stateName, routeInfo.id) + logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName) if (chipState == null) { Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState") @@ -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, logger + ) + MediaTttUtils.setIcon( + currentView.requireViewById(R.id.app_icon), + iconInfo.drawable, + iconInfo.contentDescription + ) // Text val otherDeviceName = newInfo.routeInfo.name.toString() @@ -127,7 +135,7 @@ class MediaTttChipControllerSender @Inject constructor( // Loading currentView.requireViewById(R.id.loading).visibility = - chipState.isMidTransfer.visibleIfTrue() + (chipState.transferStatus == TransferStatus.IN_PROGRESS).visibleIfTrue() // Undo val undoView = currentView.requireViewById(R.id.undo) @@ -139,12 +147,12 @@ class MediaTttChipControllerSender @Inject constructor( // Failure currentView.requireViewById(R.id.failure_icon).visibility = - chipState.isTransferFailure.visibleIfTrue() + (chipState.transferStatus == TransferStatus.FAILED).visibleIfTrue() // For accessibility currentView.requireViewById( R.id.media_ttt_sender_chip_inner - ).contentDescription = "$iconName $chipText" + ).contentDescription = "${iconInfo.contentDescription} $chipText" } override fun animateViewIn(view: ViewGroup) { @@ -162,10 +170,14 @@ class MediaTttChipControllerSender @Inject constructor( } override fun removeView(removalReason: String) { - // Don't remove the chip if we're mid-transfer since the user should still be able to - // see the status of the transfer. (But do remove it if it's finally timed out.) - if (info?.state?.isMidTransfer == true && - removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT) { + // Don't remove the chip if we're in progress or succeeded, since the user should still be + // able to see the status of the transfer. (But do remove it if it's finally timed out.) + val transferStatus = info?.state?.transferStatus + if ( + (transferStatus == TransferStatus.IN_PROGRESS || + transferStatus == TransferStatus.SUCCEEDED) && + removalReason != TemporaryDisplayRemovalReason.REASON_TIMEOUT + ) { return } super.removeView(removalReason) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt new file mode 100644 index 0000000000000..f15720df52457 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/TransferStatus.kt @@ -0,0 +1,31 @@ +/* + * 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.sender + +/** Represents the different possible transfer states that we could be in. */ +enum class TransferStatus { + /** The transfer hasn't started yet. */ + NOT_STARTED, + /** The transfer is currently ongoing but hasn't completed yet. */ + IN_PROGRESS, + /** The transfer has completed successfully. */ + SUCCEEDED, + /** The transfer has completed with a failure. */ + FAILED, + /** The device is too far away to do a transfer. */ + TOO_FAR, +} diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index 734eeecec2151..7cc90b78c0250 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -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( * 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(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" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt index d95e5c48256c0..940681544d4ef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerTest.kt @@ -23,11 +23,11 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory import com.android.systemui.log.LogcatEchoTracker import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import java.io.StringWriter import org.junit.Before import org.junit.Test import org.mockito.Mockito.mock -import java.io.PrintWriter -import java.io.StringWriter @SmallTest class MediaTttLoggerTest : SysuiTestCase() { @@ -43,11 +43,12 @@ class MediaTttLoggerTest : SysuiTestCase() { } @Test - fun logStateChange_bufferHasDeviceTypeTagAndStateNameAndId() { + fun logStateChange_bufferHasDeviceTypeTagAndParamInfo() { val stateName = "test state name" val id = "test id" + val packageName = "this.is.a.package" - logger.logStateChange(stateName, id) + logger.logStateChange(stateName, id, packageName) val stringWriter = StringWriter() buffer.dump(PrintWriter(stringWriter), tailLength = 0) @@ -56,6 +57,7 @@ class MediaTttLoggerTest : SysuiTestCase() { assertThat(actualString).contains(DEVICE_TYPE_TAG) assertThat(actualString).contains(stateName) assertThat(actualString).contains(id) + assertThat(actualString).contains(packageName) } @Test @@ -70,6 +72,19 @@ class MediaTttLoggerTest : SysuiTestCase() { assertThat(actualString).contains(DEVICE_TYPE_TAG) assertThat(actualString).contains(reason) } + + @Test + fun logPackageNotFound_bufferHasPackageName() { + val packageName = "this.is.a.package" + + logger.logPackageNotFound(packageName) + + val stringWriter = StringWriter() + buffer.dump(PrintWriter(stringWriter), tailLength = 0) + val actualString = stringWriter.toString() + + assertThat(actualString).contains(packageName) + } } private const val DEVICE_TYPE_TAG = "TEST TYPE" 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 new file mode 100644 index 0000000000000..37f6434ea0699 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt @@ -0,0 +1,138 @@ +/* + * 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 + @Mock private lateinit var logger: MediaTttLogger + + @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()) + ) + .thenThrow(PackageManager.NameNotFoundException()) + whenever( + packageManager.getApplicationInfo( + Mockito.eq(PACKAGE_NAME), + any() + ) + ) + .thenReturn(applicationInfo) + context.setMockPackageManager(packageManager) + } + + @Test + fun getIconInfoFromPackageName_nullPackageName_returnsDefault() { + val iconInfo = + MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger) + + 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", logger) + + 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, logger) + + 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" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index e7b4593b0ebba..d41ad48676b41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -173,37 +173,72 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { null ) - verify(logger).logStateChange(any(), any()) + verify(logger).logStateChange(any(), any(), any()) } @Test - fun setIcon_isAppIcon_usesAppIconSize() { - controllerReceiver.displayView(getChipReceiverInfo()) + fun updateView_noOverrides_usesInfoFromAppIcon() { + controllerReceiver.displayView( + ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride = null) + ) + + val view = getChipView() + assertThat(view.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable) + assertThat(view.getAppIconView().contentDescription).isEqualTo(APP_NAME) + } + + @Test + fun updateView_appIconOverride_usesOverride() { + val drawableOverride = context.getDrawable(R.drawable.ic_celebration)!! + + controllerReceiver.displayView( + ChipReceiverInfo(routeInfo, drawableOverride, appNameOverride = null) + ) + + val view = getChipView() + assertThat(view.getAppIconView().drawable).isEqualTo(drawableOverride) + } + + @Test + fun updateView_appNameOverride_usesOverride() { + val appNameOverride = "Sweet New App" + + controllerReceiver.displayView( + ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride) + ) + + val view = getChipView() + assertThat(view.getAppIconView().contentDescription).isEqualTo(appNameOverride) + } + + @Test + fun updateView_isAppIcon_usesAppIconSize() { + controllerReceiver.displayView(getChipReceiverInfo(packageName = PACKAGE_NAME)) val chipView = getChipView() - controllerReceiver.setIcon(chipView, PACKAGE_NAME) chipView.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - val expectedSize = controllerReceiver.getIconSize(isAppIcon = true) + val expectedSize = + context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver) assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize) assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize) } @Test - fun setIcon_notAppIcon_usesGenericIconSize() { - controllerReceiver.displayView(getChipReceiverInfo()) + fun updateView_notAppIcon_usesGenericIconSize() { + controllerReceiver.displayView(getChipReceiverInfo(packageName = null)) val chipView = getChipView() - controllerReceiver.setIcon(chipView, appPackageName = null) chipView.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - val expectedSize = controllerReceiver.getIconSize(isAppIcon = false) + val expectedSize = + context.resources.getDimensionPixelSize(R.dimen.media_ttt_generic_icon_size_receiver) assertThat(chipView.getAppIconView().measuredWidth).isEqualTo(expectedSize) assertThat(chipView.getAppIconView().measuredHeight).isEqualTo(expectedSize) } @@ -226,8 +261,13 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { return viewCaptor.value as ViewGroup } - private fun getChipReceiverInfo(): ChipReceiverInfo = - ChipReceiverInfo(routeInfo, null, null) + private fun getChipReceiverInfo(packageName: String?): ChipReceiverInfo { + val routeInfo = MediaRoute2Info.Builder("id", "Test route name") + .addFeature("feature") + .setClientPackageName(packageName) + .build() + return ChipReceiverInfo(routeInfo, null, null) + } private fun ViewGroup.getAppIconView() = this.requireViewById(R.id.app_icon) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index 52b6eed9a14d5..05440548c754f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -299,7 +299,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { null ) - verify(logger).logStateChange(any(), any()) + verify(logger).logStateChange(any(), any(), any()) } @Test @@ -590,12 +590,24 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } @Test - fun transferToReceiverTriggeredThenFarFromReceiver_eventuallyTimesOut() { - val state = transferToReceiverTriggered() - controllerSender.displayView(state) - fakeClock.advanceTime(1000L) - controllerSender.removeView("fakeRemovalReason") + fun transferToReceiverTriggeredThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToReceiverTriggered()) + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + } + + @Test + fun transferToReceiverTriggeredThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToReceiverTriggered()) + + controllerSender.removeView("fakeRemovalReason") fakeClock.advanceTime(TIMEOUT + 1L) verify(windowManager).removeView(any()) @@ -613,17 +625,97 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } @Test - fun transferToThisDeviceTriggeredThenFarFromReceiver_eventuallyTimesOut() { - val state = transferToThisDeviceTriggered() - controllerSender.displayView(state) - fakeClock.advanceTime(1000L) - controllerSender.removeView("fakeRemovalReason") + fun transferToThisDeviceTriggeredThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToThisDeviceTriggered()) + controllerSender.removeView("fakeRemovalReason") fakeClock.advanceTime(TIMEOUT + 1L) verify(windowManager).removeView(any()) } + @Test + fun transferToThisDeviceTriggeredThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToThisDeviceTriggered()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + } + + @Test + fun transferToReceiverSucceededThenRemoveView_viewStillDisplayed() { + controllerSender.displayView(transferToReceiverSucceeded()) + + controllerSender.removeView("fakeRemovalReason") + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + } + + @Test + fun transferToReceiverSucceededThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToReceiverSucceeded()) + + controllerSender.removeView("fakeRemovalReason") + fakeClock.advanceTime(TIMEOUT + 1L) + + verify(windowManager).removeView(any()) + } + + @Test + fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToReceiverSucceeded()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + } + + @Test + fun transferToThisDeviceSucceededThenRemoveView_viewStillDisplayed() { + controllerSender.displayView(transferToThisDeviceSucceeded()) + + controllerSender.removeView("fakeRemovalReason") + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + } + + @Test + fun transferToThisDeviceSucceededThenRemoveView_eventuallyTimesOut() { + controllerSender.displayView(transferToThisDeviceSucceeded()) + + controllerSender.removeView("fakeRemovalReason") + fakeClock.advanceTime(TIMEOUT + 1L) + + verify(windowManager).removeView(any()) + } + + @Test + fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayed() { + controllerSender.displayView(transferToThisDeviceSucceeded()) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null + ) + fakeExecutor.runAllReady() + + verify(windowManager, never()).removeView(any()) + } + private fun ViewGroup.getAppIconView() = this.requireViewById(R.id.app_icon) private fun ViewGroup.getChipText(): String = diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt index e616c26377d2a..c249be1549b84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt @@ -17,15 +17,10 @@ package com.android.systemui.temporarydisplay import android.content.Context -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager -import android.graphics.drawable.Drawable import android.os.PowerManager -import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager -import android.widget.ImageView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -42,9 +37,7 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test -import org.mockito.ArgumentCaptor import org.mockito.Mock -import org.mockito.Mockito.eq import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -58,11 +51,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { private lateinit var fakeClock: FakeSystemClock private lateinit var fakeExecutor: FakeExecutor - private lateinit var appIconFromPackageName: Drawable - @Mock - private lateinit var packageManager: PackageManager - @Mock - private lateinit var applicationInfo: ApplicationInfo @Mock private lateinit var logger: MediaTttLogger @Mock @@ -78,17 +66,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - 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() - )).thenThrow(PackageManager.NameNotFoundException()) - whenever(packageManager.getApplicationInfo( - eq(PACKAGE_NAME), any() - )).thenReturn(applicationInfo) - context.setMockPackageManager(packageManager) - whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())) .thenReturn(TIMEOUT_MS.toInt()) @@ -229,117 +206,8 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { verify(windowManager, never()).removeView(any()) } - @Test - fun setIcon_nullAppIconDrawableAndNullPackageName_stillHasIcon() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, appPackageName = null, appIconDrawableOverride = null) - - assertThat(view.getAppIconView().drawable).isNotNull() - } - - @Test - fun setIcon_nullAppIconDrawableAndInvalidPackageName_stillHasIcon() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon( - view, appPackageName = "fakePackageName", appIconDrawableOverride = null - ) - - assertThat(view.getAppIconView().drawable).isNotNull() - } - - @Test - fun setIcon_nullAppIconDrawable_iconIsFromPackageName() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, PACKAGE_NAME, appIconDrawableOverride = null, null) - - assertThat(view.getAppIconView().drawable).isEqualTo(appIconFromPackageName) - } - - @Test - fun setIcon_hasAppIconDrawable_iconIsDrawable() { - underTest.displayView(getState()) - val view = getView() - - val drawable = context.getDrawable(R.drawable.ic_alarm)!! - underTest.setIcon(view, PACKAGE_NAME, drawable, null) - - assertThat(view.getAppIconView().drawable).isEqualTo(drawable) - } - - @Test - fun setIcon_nullAppNameAndNullPackageName_stillHasContentDescription() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, appPackageName = null, appNameOverride = null) - - assertThat(view.getAppIconView().contentDescription.toString()).isNotEmpty() - } - - @Test - fun setIcon_nullAppNameAndInvalidPackageName_stillHasContentDescription() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon( - view, appPackageName = "fakePackageName", appNameOverride = null - ) - - assertThat(view.getAppIconView().contentDescription.toString()).isNotEmpty() - } - - @Test - fun setIcon_nullAppName_iconContentDescriptionIsFromPackageName() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, PACKAGE_NAME, null, appNameOverride = null) - - assertThat(view.getAppIconView().contentDescription).isEqualTo(APP_NAME) - } - - @Test - fun setIcon_hasAppName_iconContentDescriptionIsAppNameOverride() { - underTest.displayView(getState()) - val view = getView() - - val appName = "Override App Name" - underTest.setIcon(view, PACKAGE_NAME, null, appName) - - assertThat(view.getAppIconView().contentDescription).isEqualTo(appName) - } - - @Test - fun setIcon_iconSizeMatchesGetIconSize() { - underTest.displayView(getState()) - val view = getView() - - underTest.setIcon(view, PACKAGE_NAME) - view.measure( - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) - ) - - assertThat(view.getAppIconView().measuredWidth).isEqualTo(ICON_SIZE) - assertThat(view.getAppIconView().measuredHeight).isEqualTo(ICON_SIZE) - } - private fun getState(name: String = "name") = ViewInfo(name) - private fun getView(): ViewGroup { - val viewCaptor = ArgumentCaptor.forClass(View::class.java) - verify(windowManager).addView(viewCaptor.capture(), any()) - return viewCaptor.value as ViewGroup - } - - private fun ViewGroup.getAppIconView() = this.requireViewById(R.id.app_icon) - private fun getConfigurationListener(): ConfigurationListener { val callbackCaptor = argumentCaptor() verify(configurationController).addCallback(capture(callbackCaptor)) @@ -371,7 +239,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { super.updateView(newInfo, currentView) mostRecentViewInfo = newInfo } - override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE } inner class ViewInfo(val name: String) : TemporaryViewInfo { @@ -379,7 +246,4 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() { } } -private const val PACKAGE_NAME = "com.android.systemui" -private const val APP_NAME = "Fake App Name" private const val TIMEOUT_MS = 10000L -private const val ICON_SIZE = 47