[Chipbar] Remove the media-specific wake reason and window title from

the ChipbarCoordinator and instead pass them in each time we display a
temporary view.

Now, TemporaryViewDisplayController needs to handle different
information being displayed with different window titles. That logic has
been added and tested.

Bug: 245610654
Test: manual: Verified that media ttt views still display correctly
Test: manual: Verified that showing a new media ttt view with a new
title will animate out the old view and display the new one
Test: atest TemporaryViewDisplayControllerTest

Change-Id: I5b324867c3e8b0cc040217c21ca94f26ad8c351a
This commit is contained in:
Caitlin Shkuratov
2022-10-24 17:54:05 +00:00
parent c85930059e
commit 13c529d3d9
11 changed files with 135 additions and 60 deletions

View File

@@ -27,10 +27,11 @@ import com.android.systemui.common.shared.model.Icon
/** Utility methods for media tap-to-transfer. */
class MediaTttUtils {
companion object {
// Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
// UpdateMediaTapToTransferReceiverDisplayTest
const val WINDOW_TITLE = "Media Transfer Chip View"
const val WAKE_REASON = "MEDIA_TRANSFER_ACTIVATED"
const val WINDOW_TITLE_SENDER = "Media Transfer Chip View (Sender)"
const val WINDOW_TITLE_RECEIVER = "Media Transfer Chip View (Receiver)"
const val WAKE_REASON_SENDER = "MEDIA_TRANSFER_ACTIVATED_SENDER"
const val WAKE_REASON_RECEIVER = "MEDIA_TRANSFER_ACTIVATED_RECEIVER"
/**
* Returns the information needed to display the icon in [Icon] form.

View File

@@ -40,7 +40,6 @@ 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
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
@@ -78,8 +77,6 @@ class MediaTttChipControllerReceiver @Inject constructor(
configurationController,
powerManager,
R.layout.media_ttt_chip_receiver,
MediaTttUtils.WINDOW_TITLE,
MediaTttUtils.WAKE_REASON,
) {
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -231,7 +228,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
data class ChipReceiverInfo(
val routeInfo: MediaRoute2Info,
val appIconDrawableOverride: Drawable?,
val appNameOverride: CharSequence?
) : TemporaryViewInfo {
override fun getTimeoutMs() = DEFAULT_TIMEOUT_MILLIS
}
val appNameOverride: CharSequence?,
override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
) : TemporaryViewInfo()

View File

@@ -43,7 +43,7 @@ enum class ChipStateSender(
@StringRes val stringResId: Int?,
val transferStatus: TransferStatus,
val endItem: SenderEndItem?,
val timeout: Long = DEFAULT_TIMEOUT_MILLIS
val timeout: Int = DEFAULT_TIMEOUT_MILLIS,
) {
/**
* A state representing that the two devices are close but not close enough to *start* a cast to
@@ -223,6 +223,6 @@ sealed class SenderEndItem {
// Give the Transfer*Triggered states a longer timeout since those states represent an active
// process and we should keep the user informed about it as long as possible (but don't allow it to
// continue indefinitely).
private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000L
private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000
private const val TAG = "ChipStateSender"

View File

@@ -159,6 +159,9 @@ constructor(
}
},
vibrationEffect = chipStateSender.transferStatus.vibrationEffect,
windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
timeoutMs = chipStateSender.timeout,
)
}

View File

@@ -45,11 +45,6 @@ import com.android.systemui.util.concurrency.DelayableExecutor
*
* The generic type T is expected to contain all the information necessary for the subclasses to
* display the view in a certain state, since they receive <T> in [updateView].
*
* @property windowTitle the title to use for the window that displays the temporary view. Should be
* normally cased, like "Window Title".
* @property wakeReason a string used for logging if we needed to wake the screen in order to
* display the temporary view. Should be screaming snake cased, like WAKE_REASON.
*/
abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger>(
internal val context: Context,
@@ -60,8 +55,6 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
private val configurationController: ConfigurationController,
private val powerManager: PowerManager,
@LayoutRes private val viewLayoutRes: Int,
private val windowTitle: String,
private val wakeReason: String,
) : CoreStartable {
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
@@ -74,7 +67,6 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
title = windowTitle
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
}
@@ -102,20 +94,31 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
fun displayView(newInfo: T) {
val currentDisplayInfo = displayInfo
if (currentDisplayInfo != null) {
if (currentDisplayInfo != null &&
currentDisplayInfo.info.windowTitle == newInfo.windowTitle) {
// We're already displaying information in the correctly-titled window, so we just need
// to update the view.
currentDisplayInfo.info = newInfo
updateView(currentDisplayInfo.info, currentDisplayInfo.view)
} else {
// The view is new, so set up all our callbacks and inflate the view
if (currentDisplayInfo != null) {
// We're already displaying information but that information is under a different
// window title. So, we need to remove the old window with the old title and add a
// new window with the new title.
removeView(removalReason = "New info has new window title: ${newInfo.windowTitle}")
}
// At this point, we're guaranteed to no longer be displaying a view.
// So, set up all our callbacks and inflate the view.
configurationController.addCallback(displayScaleListener)
// Wake the screen if necessary so the user will see the view. (Per b/239426653, we want
// the view to show over the dream state, so we should only wake up if the screen is
// completely off.)
if (!powerManager.isScreenOn) {
powerManager.wakeUp(
SystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_APPLICATION,
"com.android.systemui:$wakeReason",
SystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_APPLICATION,
"com.android.systemui:${newInfo.wakeReason}",
)
}
logger.logChipAddition()
@@ -124,7 +127,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
// Cancel and re-set the view timeout each time we get a new state.
val timeout = accessibilityManager.getRecommendedTimeoutMillis(
newInfo.getTimeoutMs().toInt(),
newInfo.timeoutMs,
// Not all views have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
// include it just to be safe.
FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
@@ -149,7 +152,12 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
val newDisplayInfo = DisplayInfo(newView, newInfo)
displayInfo = newDisplayInfo
updateView(newDisplayInfo.info, newDisplayInfo.view)
windowManager.addView(newView, windowLayoutParams)
val paramsWithTitle = WindowManager.LayoutParams().also {
it.copyFrom(windowLayoutParams)
it.title = newInfo.windowTitle
}
windowManager.addView(newView, paramsWithTitle)
animateViewIn(newView)
}

View File

@@ -19,12 +19,24 @@ package com.android.systemui.temporarydisplay
/**
* A superclass view state used with [TemporaryViewDisplayController].
*/
interface TemporaryViewInfo {
abstract class TemporaryViewInfo {
/**
* Returns the amount of time the given view state should display on the screen before it times
* out and disappears.
* The title to use for the window that displays the temporary view. Should be normally cased,
* like "Window Title".
*/
fun getTimeoutMs(): Long = DEFAULT_TIMEOUT_MILLIS
abstract val windowTitle: String
/**
* A string used for logging if we needed to wake the screen in order to display the temporary
* view. Should be screaming snake cased, like WAKE_REASON.
*/
abstract val wakeReason: String
/**
* The amount of time the given view state should display on the screen before it times out and
* disappears.
*/
open val timeoutMs: Int = DEFAULT_TIMEOUT_MILLIS
}
const val DEFAULT_TIMEOUT_MILLIS = 10000L
const val DEFAULT_TIMEOUT_MILLIS = 10000

View File

@@ -31,6 +31,6 @@ open class TemporaryViewLogger(
/** Logs that we removed the chip for the given [reason]. */
fun logChipRemoval(reason: String) {
buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "Chip removed due to $str1" })
buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "Chip removed due to: $str1" })
}
}

View File

@@ -39,7 +39,6 @@ import com.android.systemui.common.ui.binder.TextViewBinder
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.media.taptotransfer.sender.MediaTttSenderLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
@@ -90,8 +89,6 @@ open class ChipbarCoordinator @Inject constructor(
configurationController,
powerManager,
R.layout.chipbar,
MediaTttUtils.WINDOW_TITLE,
MediaTttUtils.WAKE_REASON,
) {
private lateinit var parent: ChipbarRootView

View File

@@ -37,7 +37,10 @@ data class ChipbarInfo(
val text: Text,
val endItem: ChipbarEndItem?,
val vibrationEffect: VibrationEffect? = null,
) : TemporaryViewInfo
override val windowTitle: String,
override val wakeReason: String,
override val timeoutMs: Int,
) : TemporaryViewInfo()
/** The possible items to display at the end of the chipbar. */
sealed class ChipbarEndItem {

View File

@@ -41,6 +41,7 @@ import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -85,10 +86,17 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
}
@Test
fun displayView_viewAdded() {
underTest.displayView(getState())
fun displayView_viewAddedWithCorrectTitle() {
underTest.displayView(
ViewInfo(
name = "name",
windowTitle = "Fake Window Title",
)
)
verify(windowManager).addView(any(), any())
val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
verify(windowManager).addView(any(), capture(windowParamsCaptor))
assertThat(windowParamsCaptor.value!!.title).isEqualTo("Fake Window Title")
}
@Test
@@ -110,7 +118,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
}
@Test
fun displayView_twice_viewNotAddedTwice() {
fun displayView_twiceWithSameWindowTitle_viewNotAddedTwice() {
underTest.displayView(getState())
reset(windowManager)
@@ -118,6 +126,32 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
verify(windowManager, never()).addView(any(), any())
}
@Test
fun displayView_twiceWithDifferentWindowTitles_oldViewRemovedNewViewAdded() {
underTest.displayView(
ViewInfo(
name = "name",
windowTitle = "First Fake Window Title",
)
)
underTest.displayView(
ViewInfo(
name = "name",
windowTitle = "Second Fake Window Title",
)
)
val viewCaptor = argumentCaptor<View>()
val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor))
assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("First Fake Window Title")
assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title")
verify(windowManager).removeView(viewCaptor.allValues[0])
}
@Test
fun displayView_viewDoesNotDisappearsBeforeTimeout() {
val state = getState()
@@ -232,8 +266,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
configurationController,
powerManager,
R.layout.chipbar,
"Window Title",
"WAKE_REASON",
) {
var mostRecentViewInfo: ViewInfo? = null
@@ -250,9 +282,12 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
}
}
inner class ViewInfo(val name: String) : TemporaryViewInfo {
override fun getTimeoutMs() = 1L
}
inner class ViewInfo(
val name: String,
override val windowTitle: String = "Window Title",
override val wakeReason: String = "WAKE_REASON",
override val timeoutMs: Int = 1
) : TemporaryViewInfo()
}
private const val TIMEOUT_MS = 10000L

View File

@@ -105,7 +105,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
val drawable = context.getDrawable(R.drawable.ic_celebration)!!
underTest.displayView(
ChipbarInfo(
createChipbarInfo(
Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
Text.Loaded("text"),
endItem = null,
@@ -121,7 +121,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
fun displayView_resourceIcon_correctlyRendered() {
val contentDescription = ContentDescription.Resource(R.string.controls_error_timeout)
underTest.displayView(
ChipbarInfo(
createChipbarInfo(
Icon.Resource(R.drawable.ic_cake, contentDescription),
Text.Loaded("text"),
endItem = null,
@@ -136,7 +136,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_loadedText_correctlyRendered() {
underTest.displayView(
ChipbarInfo(
createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("display view text here"),
endItem = null,
@@ -149,7 +149,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_resourceText_correctlyRendered() {
underTest.displayView(
ChipbarInfo(
createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Resource(R.string.screenrecord_start_error),
endItem = null,
@@ -163,7 +163,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_endItemNull_correctlyRendered() {
underTest.displayView(
ChipbarInfo(
createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = null,
@@ -179,7 +179,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_endItemLoading_correctlyRendered() {
underTest.displayView(
ChipbarInfo(
createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = ChipbarEndItem.Loading,
@@ -195,7 +195,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_endItemError_correctlyRendered() {
underTest.displayView(
ChipbarInfo(
createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = ChipbarEndItem.Error,
@@ -211,7 +211,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_endItemButton_correctlyRendered() {
underTest.displayView(
ChipbarInfo(
createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem =
@@ -237,7 +237,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
val buttonClickListener = View.OnClickListener { isClicked = true }
underTest.displayView(
ChipbarInfo(
createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem =
@@ -260,7 +260,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
val buttonClickListener = View.OnClickListener { isClicked = true }
underTest.displayView(
ChipbarInfo(
createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem =
@@ -279,7 +279,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_vibrationEffect_doubleClickEffect() {
underTest.displayView(
ChipbarInfo(
createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = null,
@@ -296,7 +296,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
val drawable = context.getDrawable(R.drawable.ic_celebration)!!
underTest.displayView(
ChipbarInfo(
createChipbarInfo(
Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
Text.Loaded("title text"),
endItem = ChipbarEndItem.Loading,
@@ -314,7 +314,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
// WHEN the view is updated
val newDrawable = context.getDrawable(R.drawable.ic_cake)!!
underTest.updateView(
ChipbarInfo(
createChipbarInfo(
Icon.Loaded(newDrawable, ContentDescription.Loaded("new CD")),
Text.Loaded("new title text"),
endItem = ChipbarEndItem.Error,
@@ -331,6 +331,23 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
}
private fun createChipbarInfo(
startIcon: Icon,
text: Text,
endItem: ChipbarEndItem?,
vibrationEffect: VibrationEffect? = null,
): ChipbarInfo {
return ChipbarInfo(
startIcon,
text,
endItem,
vibrationEffect,
windowTitle = WINDOW_TITLE,
wakeReason = WAKE_REASON,
timeoutMs = TIMEOUT,
)
}
private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon)
private fun ViewGroup.getChipText(): String =
@@ -350,3 +367,5 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
}
private const val TIMEOUT = 10000
private const val WINDOW_TITLE = "Test Chipbar Window Title"
private const val WAKE_REASON = "TEST_CHIPBAR_WAKE_REASON"