Merge changes I5b86508e,I01cd3be5,I5f21d737,Ibae88dbf into tm-qpr-dev

* changes:
  [Media TTT] Put package error logs in the log buffer with all the other logs.
  [Media TTT] Also log the package name on each state change.
  [Media TTT] Ignore the FAR_FROM_RECEIVER event if we're in the succeeded state.
  [Chipbar] Remove #setIcon from TemporaryViewDisplayController and put it in MediaTttUtils instead.
This commit is contained in:
TreeHugger Robot
2022-09-16 00:15:23 +00:00
committed by Android (Google) Code Review
12 changed files with 522 additions and 275 deletions

View File

@@ -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"

View File

@@ -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
)

View File

@@ -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)

View File

@@ -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,
);
/**

View File

@@ -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<View>(R.id.loading).visibility =
chipState.isMidTransfer.visibleIfTrue()
(chipState.transferStatus == TransferStatus.IN_PROGRESS).visibleIfTrue()
// Undo
val undoView = currentView.requireViewById<View>(R.id.undo)
@@ -139,12 +147,12 @@ class MediaTttChipControllerSender @Inject constructor(
// Failure
currentView.requireViewById<View>(R.id.failure_icon).visibility =
chipState.isTransferFailure.visibleIfTrue()
(chipState.transferStatus == TransferStatus.FAILED).visibleIfTrue()
// For accessibility
currentView.requireViewById<ViewGroup>(
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)

View File

@@ -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,
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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<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, 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"

View File

@@ -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<ImageView>(R.id.app_icon)
}

View File

@@ -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<ImageView>(R.id.app_icon)
private fun ViewGroup.getChipText(): String =

View File

@@ -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<PackageManager.ApplicationInfoFlags>()
)).thenThrow(PackageManager.NameNotFoundException())
whenever(packageManager.getApplicationInfo(
eq(PACKAGE_NAME), any<PackageManager.ApplicationInfoFlags>()
)).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<ImageView>(R.id.app_icon)
private fun getConfigurationListener(): ConfigurationListener {
val callbackCaptor = argumentCaptor<ConfigurationListener>()
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