From 0cf59998850bfb197548d689b47d56257f4eb2ea Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Fri, 20 Jan 2023 18:28:36 +0000 Subject: [PATCH 1/3] [Media TTT] Log changes to the sender's current state map. Also logs the current map when a dump occurs. Fixes: 265455911 Test: Manual: See sample logs in bug Test: atest MediaTttSenderCoordinatorTest MediaTttSenderLoggerTest Change-Id: I9487cfdc3bf90c4109b838f141f20121b18f1674 --- .../sender/MediaTttSenderCoordinator.kt | 25 +++++++++++++----- .../sender/MediaTttSenderLogger.kt | 23 ++++++++++++++++ .../TemporaryViewDisplayController.kt | 7 ++--- .../sender/MediaTttSenderCoordinatorTest.kt | 16 +++++++++--- .../sender/MediaTttSenderLoggerTest.kt | 26 +++++++++++++++++++ .../TemporaryViewDisplayControllerTest.kt | 2 +- 6 files changed, 86 insertions(+), 13 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt index b2a5e356b9360..d59967917e6cf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt @@ -23,9 +23,11 @@ import android.view.View import com.android.internal.logging.UiEventLogger import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.CoreStartable +import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.media.taptotransfer.common.MediaTttUtils import com.android.systemui.statusbar.CommandQueue @@ -34,6 +36,7 @@ import com.android.systemui.temporarydisplay.ViewPriority import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo +import java.io.PrintWriter import javax.inject.Inject /** @@ -47,14 +50,14 @@ constructor( private val chipbarCoordinator: ChipbarCoordinator, private val commandQueue: CommandQueue, private val context: Context, + private val dumpManager: DumpManager, private val logger: MediaTttSenderLogger, private val mediaTttFlags: MediaTttFlags, private val uiEventLogger: MediaTttSenderUiEventLogger, -) : CoreStartable { +) : CoreStartable, Dumpable { private var displayedState: ChipStateSender? = null // A map to store current chip state per id. - // TODO(b/265455911): Log whenever we add or remove from the store. private var stateMap: MutableMap = mutableMapOf() private val commandQueueCallbacks = @@ -75,6 +78,7 @@ constructor( override fun start() { if (mediaTttFlags.isMediaTttEnabled()) { commandQueue.addCallback(commandQueueCallbacks) + dumpManager.registerNormalDumpable(this) } } @@ -104,12 +108,13 @@ constructor( uiEventLogger.logSenderStateChange(chipState) if (chipState == ChipStateSender.FAR_FROM_RECEIVER) { + val removalReason = ChipStateSender.FAR_FROM_RECEIVER.name + // No need to store the state since it is the default state - removeIdFromStore(routeInfo.id) + removeIdFromStore(routeInfo.id, reason = removalReason) // Return early if we're not displaying a chip anyway val currentDisplayedState = displayedState ?: return - val removalReason = ChipStateSender.FAR_FROM_RECEIVER.name if ( currentDisplayedState.transferStatus == TransferStatus.IN_PROGRESS || currentDisplayedState.transferStatus == TransferStatus.SUCCEEDED @@ -127,6 +132,7 @@ constructor( chipbarCoordinator.removeView(routeInfo.id, removalReason) } else { stateMap[routeInfo.id] = chipState + logger.logStateMap(stateMap) displayedState = chipState chipbarCoordinator.registerListener(displayListener) chipbarCoordinator.displayView( @@ -232,12 +238,19 @@ constructor( } private val displayListener = - TemporaryViewDisplayController.Listener { id -> removeIdFromStore(id) } + TemporaryViewDisplayController.Listener { id, reason -> removeIdFromStore(id, reason) } - private fun removeIdFromStore(id: String) { + private fun removeIdFromStore(id: String, reason: String) { + logger.logStateMapRemoval(id, reason) stateMap.remove(id) + logger.logStateMap(stateMap) if (stateMap.isEmpty()) { chipbarCoordinator.unregisterListener(displayListener) } } + + override fun dump(pw: PrintWriter, args: Array) { + pw.println("Current sender states:") + pw.println(stateMap.toString()) + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt index 6363425d6db98..964a95b9be9a8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt @@ -85,6 +85,29 @@ constructor( ) } + /** Logs the current contents of the state map. */ + fun logStateMap(map: Map) { + buffer.log( + TAG, + LogLevel.DEBUG, + { str1 = map.toString() }, + { "Current sender states: $str1" } + ) + } + + /** Logs that [id] has been removed from the state map due to [reason]. */ + fun logStateMapRemoval(id: String, reason: String) { + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = id + str2 = reason + }, + { "State removal: id=$str1 reason=$str2" } + ) + } + companion object { private const val TAG = "MediaTttSender" } diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt index df8d16142b8bd..1065d3349930d 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt @@ -327,7 +327,7 @@ abstract class TemporaryViewDisplayController - listener.onInfoPermanentlyRemoved(it.info.id) + listener.onInfoPermanentlyRemoved(it.info.id, REMOVAL_REASON_TIME_EXPIRED) } } } @@ -457,7 +457,7 @@ abstract class TemporaryViewDisplayController() - override fun onInfoPermanentlyRemoved(id: String) { + override fun onInfoPermanentlyRemoved(id: String, reason: String) { permanentlyRemovedIds.add(id) } } From 18c1b47f249cbf4b5354ce0575e8e7f6aca5f323 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Mon, 23 Jan 2023 16:41:00 +0000 Subject: [PATCH 2/3] [Media TTT] If the FAR state is ignored, don't update the store. Fixes: 266217596 Test: manual: TRIGGERED -> FAR -> SUCCEEDED ==> verify SUCCEEDED chipbar is displayed Test: atest MediaTttSenderCoordinatorTest Change-Id: Iac4d46ec15962c28270307af21b4f2f10d398c56 --- .../sender/MediaTttSenderCoordinator.kt | 7 +- .../sender/MediaTttSenderCoordinatorTest.kt | 99 ++++++++++++++++++- 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt index d59967917e6cf..e5b9a03718bd0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt @@ -108,13 +108,10 @@ constructor( uiEventLogger.logSenderStateChange(chipState) if (chipState == ChipStateSender.FAR_FROM_RECEIVER) { - val removalReason = ChipStateSender.FAR_FROM_RECEIVER.name - - // No need to store the state since it is the default state - removeIdFromStore(routeInfo.id, reason = removalReason) // Return early if we're not displaying a chip anyway val currentDisplayedState = displayedState ?: return + val removalReason = ChipStateSender.FAR_FROM_RECEIVER.name if ( currentDisplayedState.transferStatus == TransferStatus.IN_PROGRESS || currentDisplayedState.transferStatus == TransferStatus.SUCCEEDED @@ -128,6 +125,8 @@ constructor( return } + // No need to store the state since it is the default state + removeIdFromStore(routeInfo.id, reason = removalReason) displayedState = null chipbarCoordinator.removeView(routeInfo.id, removalReason) } else { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt index dddc0057a5de9..68eccd7abcbc1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt @@ -742,6 +742,99 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { verify(windowManager, never()).addView(any(), any()) } + /** Regression test for b/266217596. */ + @Test + fun toReceiver_triggeredThenFar_thenSucceeded_updatesToSucceeded() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + routeInfo, + null, + ) + + // WHEN a FAR command comes in + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null, + ) + + // THEN it is ignored and the chipbar is stilled displayed + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) + verify(windowManager, never()).removeView(any()) + + // WHEN a SUCCEEDED command comes in + val succeededRouteInfo = + MediaRoute2Info.Builder(DEFAULT_ID, "Tablet Succeeded") + .addFeature("feature") + .setClientPackageName(PACKAGE_NAME) + .build() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + succeededRouteInfo, + /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() { + override fun onUndoTriggered() {} + }, + ) + + // THEN it is *not* marked as an invalid transition and the chipbar updates to the succeeded + // state. (The "invalid transition" would be FAR => SUCCEEDED.) + assertThat(chipbarView.getChipText()) + .isEqualTo( + ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText( + "Tablet Succeeded" + ) + ) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE) + } + + /** Regression test for b/266217596. */ + @Test + fun toThisDevice_triggeredThenFar_thenSucceeded_updatesToSucceeded() { + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + routeInfo, + null, + ) + + // WHEN a FAR command comes in + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + routeInfo, + null, + ) + + // THEN it is ignored and the chipbar is stilled displayed + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) + verify(windowManager, never()).removeView(any()) + + // WHEN a SUCCEEDED command comes in + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + /* undoCallback= */ object : IUndoMediaTransferCallback.Stub() { + override fun onUndoTriggered() {} + }, + ) + + // THEN it is *not* marked as an invalid transition and the chipbar updates to the succeeded + // state. (The "invalid transition" would be FAR => SUCCEEDED.) + assertThat(chipbarView.getChipText()) + .isEqualTo( + ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText( + "Tablet Succeeded" + ) + ) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) + assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.VISIBLE) + } + @Test fun receivesNewStateFromCommandQueue_isLogged() { commandQueueCallback.updateMediaTapToTransferSenderDisplay( @@ -1117,8 +1210,10 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.end_button) - private fun ChipStateSender.getExpectedStateText(): String? { - return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context) + private fun ChipStateSender.getExpectedStateText( + otherDeviceName: String = OTHER_DEVICE_NAME, + ): String? { + return this.getChipTextString(context, otherDeviceName).loadText(context) } // display receiver triggered state helper method to make sure we start from a valid state From 8a86dc702873803a4cd29b218fab0e21fbfc4442 Mon Sep 17 00:00:00 2001 From: Caitlin Shkuratov Date: Mon, 23 Jan 2023 18:41:54 +0000 Subject: [PATCH 3/3] [Media TTT] Remove `displayedState` and always use the map instead. Two bugs around `displayedState`: 1) We weren't correctly re-setting it on timeout. 2) We weren't taking IDs into account when seeing if we could ignore a FAR_FROM_RECEIVER. This CL just removes `displayedState` entirely and uses `stateMap` as the one source of truth. Note: All the test cases added actually pass without any changes to `MediaTttSenderCoordinator` because we had safeguards in other parts of the code. When I wrote the tests and included assertions about `displayedState`, then they failed. But, now that `displayedState` is gone, we can't have those sorts of assertions anymore. Fixes: 266218672 Test: TRIGGERED -> SUCCEEDED -> {wait for timeout} -> FAR ==> nothing in the logs about "removal was ignored because transferStatus=SUCCEEDED" Test: ALMOST for id=1 -> ALMOST for id=2 -> FAR for id=1 ==> chip stays, state map just has id=2 Test: atest MediaTttSenderCoordinatorTest Change-Id: I58955fd8c4a60842476102a6c504ca987d93739d --- .../sender/MediaTttSenderCoordinator.kt | 19 +- .../sender/MediaTttSenderCoordinatorTest.kt | 162 ++++++++++++++++++ 2 files changed, 170 insertions(+), 11 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt index e5b9a03718bd0..89ca5d33645c2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt @@ -56,7 +56,6 @@ constructor( private val uiEventLogger: MediaTttSenderUiEventLogger, ) : CoreStartable, Dumpable { - private var displayedState: ChipStateSender? = null // A map to store current chip state per id. private var stateMap: MutableMap = mutableMapOf() @@ -96,11 +95,11 @@ constructor( return } - val currentState = stateMap[routeInfo.id] - if (!ChipStateSender.isValidStateTransition(currentState, chipState)) { + val currentStateForId: ChipStateSender? = stateMap[routeInfo.id] + if (!ChipStateSender.isValidStateTransition(currentStateForId, chipState)) { // ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state. logger.logInvalidStateTransitionError( - currentState = currentState?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name, + currentState = currentStateForId?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name, chipState.name ) return @@ -108,31 +107,29 @@ constructor( uiEventLogger.logSenderStateChange(chipState) if (chipState == ChipStateSender.FAR_FROM_RECEIVER) { - // Return early if we're not displaying a chip anyway - val currentDisplayedState = displayedState ?: return + // Return early if we're not displaying a chip for this ID anyway + if (currentStateForId == null) return val removalReason = ChipStateSender.FAR_FROM_RECEIVER.name if ( - currentDisplayedState.transferStatus == TransferStatus.IN_PROGRESS || - currentDisplayedState.transferStatus == TransferStatus.SUCCEEDED + currentStateForId.transferStatus == TransferStatus.IN_PROGRESS || + currentStateForId.transferStatus == TransferStatus.SUCCEEDED ) { // 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. logger.logRemovalBypass( removalReason, - bypassReason = "transferStatus=${currentDisplayedState.transferStatus.name}" + bypassReason = "transferStatus=${currentStateForId.transferStatus.name}" ) return } // No need to store the state since it is the default state removeIdFromStore(routeInfo.id, reason = removalReason) - displayedState = null chipbarCoordinator.removeView(routeInfo.id, removalReason) } else { stateMap[routeInfo.id] = chipState logger.logStateMap(stateMap) - displayedState = chipState chipbarCoordinator.registerListener(displayListener) chipbarCoordinator.displayView( createChipbarInfo( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt index 68eccd7abcbc1..400a504e4d023 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt @@ -1193,6 +1193,168 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { verify(logger).logStateMapRemoval("route1", "reason") } + /** Regression test for b/266218672. */ + @Test + fun twoIdsDisplayed_oldIdIsFar_viewStillDisplayed() { + // WHEN there are two different media transfers with different IDs + val route1 = + MediaRoute2Info.Builder("route1", OTHER_DEVICE_NAME) + .addFeature("feature") + .setClientPackageName(PACKAGE_NAME) + .build() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, + route1, + null, + ) + verify(windowManager).addView(any(), any()) + reset(windowManager) + + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + MediaRoute2Info.Builder("route2", "Route 2 name") + .addFeature("feature") + .setClientPackageName(PACKAGE_NAME) + .build(), + null, + ) + val newView = getChipbarView() + + // WHEN there's a FAR event for the earlier one + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER, + route1, + null, + ) + + // THEN it's ignored and the more recent one is still displayed + assertThat(newView.getChipText()) + .isEqualTo( + ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText("Route 2 name") + ) + } + + /** Regression test for b/266218672. */ + @Test + fun receiverSucceededThenTimedOut_internalStateResetAndCanDisplayAlmostCloseToEnd() { + displayReceiverTriggered() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + null, + ) + + fakeClock.advanceTime(TIMEOUT + 1L) + verify(windowManager).removeView(any()) + + reset(windowManager) + + // WHEN we try to show ALMOST_CLOSE_TO_END + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST, + routeInfo, + null, + ) + + // THEN it succeeds + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_END_CAST.getExpectedStateText()) + } + + /** Regression test for b/266218672. */ + @Test + fun receiverSucceededThenTimedOut_internalStateResetAndCanDisplayReceiverTriggered() { + displayReceiverTriggered() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED, + routeInfo, + null, + ) + + fakeClock.advanceTime(TIMEOUT + 1L) + verify(windowManager).removeView(any()) + + reset(windowManager) + + // WHEN we try to show RECEIVER_TRIGGERED + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED, + routeInfo, + null, + ) + + // THEN it succeeds + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_TRIGGERED.getExpectedStateText()) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) + } + + /** Regression test for b/266218672. */ + @Test + fun toThisDeviceSucceededThenTimedOut_internalStateResetAndCanDisplayAlmostCloseToStart() { + displayThisDeviceTriggered() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + null, + ) + + fakeClock.advanceTime(TIMEOUT + 1L) + verify(windowManager).removeView(any()) + + reset(windowManager) + + // WHEN we try to show ALMOST_CLOSE_TO_START + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST, + routeInfo, + null, + ) + + // THEN it succeeds + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo(ChipStateSender.ALMOST_CLOSE_TO_START_CAST.getExpectedStateText()) + } + + /** Regression test for b/266218672. */ + @Test + fun toThisDeviceSucceededThenTimedOut_internalStateResetAndCanDisplayThisDeviceTriggered() { + displayThisDeviceTriggered() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED, + routeInfo, + null, + ) + + fakeClock.advanceTime(TIMEOUT + 1L) + verify(windowManager).removeView(any()) + + reset(windowManager) + + // WHEN we try to show THIS_DEVICE_TRIGGERED + val newRouteInfo = + MediaRoute2Info.Builder(DEFAULT_ID, "New Name") + .addFeature("feature") + .setClientPackageName(PACKAGE_NAME) + .build() + commandQueueCallback.updateMediaTapToTransferSenderDisplay( + StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED, + newRouteInfo, + null, + ) + + // THEN it succeeds + val chipbarView = getChipbarView() + assertThat(chipbarView.getChipText()) + .isEqualTo( + ChipStateSender.TRANSFER_TO_THIS_DEVICE_TRIGGERED.getExpectedStateText("New Name") + ) + assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) + } + private fun getChipbarView(): ViewGroup { val viewCaptor = ArgumentCaptor.forClass(View::class.java) verify(windowManager).addView(viewCaptor.capture(), any())