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

Commit b4734a22 authored by Michael Mikhail's avatar Michael Mikhail Committed by Automerger Merge Worker
Browse files

Merge "[Media TTT] Handle invalid chip transitions" into tm-qpr-dev am: 88aec368 am: d75a26d0

parents 1a34eba3 d75a26d0
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
@@ -58,6 +58,27 @@ class MediaTttLogger(
        )
    }

    /**
     * Logs an invalid sender state transition error in trying to update to [desiredState].
     *
     * @param currentState the previous state of the chip.
     * @param desiredState the new state of the chip.
     */
    fun logInvalidStateTransitionError(
        currentState: String,
        desiredState: String
    ) {
        buffer.log(
                tag,
                LogLevel.ERROR,
                {
                    str1 = currentState
                    str2 = desiredState
                },
                { "Cannot display state=$str2 after state=$str1; invalid transition" }
        )
    }

    /** Logs that we couldn't find information for [packageName]. */
    fun logPackageNotFound(packageName: String) {
        buffer.log(
+87 −8
Original line number Diff line number Diff line
@@ -56,7 +56,12 @@ enum class ChipStateSender(
        R.string.media_move_closer_to_start_cast,
        transferStatus = TransferStatus.NOT_STARTED,
        endItem = null,
    ),
    ) {
        override fun isValidNextState(nextState: ChipStateSender): Boolean {
            return nextState == FAR_FROM_RECEIVER ||
                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
        }
    },

    /**
     * A state representing that the two devices are close but not close enough to *end* a cast
@@ -70,7 +75,12 @@ enum class ChipStateSender(
        R.string.media_move_closer_to_end_cast,
        transferStatus = TransferStatus.NOT_STARTED,
        endItem = null,
    ),
    ) {
        override fun isValidNextState(nextState: ChipStateSender): Boolean {
            return nextState == FAR_FROM_RECEIVER ||
                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
        }
    },

    /**
     * A state representing that a transfer to the receiver device has been initiated (but not
@@ -83,7 +93,13 @@ enum class ChipStateSender(
        transferStatus = TransferStatus.IN_PROGRESS,
        endItem = SenderEndItem.Loading,
        timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
    ),
    ) {
        override fun isValidNextState(nextState: ChipStateSender): Boolean {
            return nextState == FAR_FROM_RECEIVER ||
                    nextState == TRANSFER_TO_RECEIVER_SUCCEEDED ||
                    nextState == TRANSFER_TO_RECEIVER_FAILED
        }
    },

    /**
     * A state representing that a transfer from the receiver device and back to this device (the
@@ -96,7 +112,13 @@ enum class ChipStateSender(
        transferStatus = TransferStatus.IN_PROGRESS,
        endItem = SenderEndItem.Loading,
        timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
    ),
    ) {
        override fun isValidNextState(nextState: ChipStateSender): Boolean {
            return nextState == FAR_FROM_RECEIVER ||
                    nextState == TRANSFER_TO_THIS_DEVICE_SUCCEEDED ||
                    nextState == TRANSFER_TO_THIS_DEVICE_FAILED
        }
    },

    /**
     * A state representing that a transfer to the receiver device has been successfully completed.
@@ -112,7 +134,13 @@ enum class ChipStateSender(
            newState =
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED
        ),
    ),
    ) {
        override fun isValidNextState(nextState: ChipStateSender): Boolean {
            return nextState == FAR_FROM_RECEIVER ||
                    nextState == ALMOST_CLOSE_TO_START_CAST ||
                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
        }
    },

    /**
     * A state representing that a transfer back to this device has been successfully completed.
@@ -128,7 +156,13 @@ enum class ChipStateSender(
            newState =
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED
        ),
    ),
    ) {
        override fun isValidNextState(nextState: ChipStateSender): Boolean {
            return nextState == FAR_FROM_RECEIVER ||
                    nextState == ALMOST_CLOSE_TO_END_CAST ||
                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
        }
    },

    /** A state representing that a transfer to the receiver device has failed. */
    TRANSFER_TO_RECEIVER_FAILED(
@@ -137,7 +171,13 @@ enum class ChipStateSender(
        R.string.media_transfer_failed,
        transferStatus = TransferStatus.FAILED,
        endItem = SenderEndItem.Error,
    ),
    ) {
        override fun isValidNextState(nextState: ChipStateSender): Boolean {
            return nextState == FAR_FROM_RECEIVER ||
                    nextState == ALMOST_CLOSE_TO_START_CAST ||
                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
        }
    },

    /** A state representing that a transfer back to this device has failed. */
    TRANSFER_TO_THIS_DEVICE_FAILED(
@@ -146,7 +186,13 @@ enum class ChipStateSender(
        R.string.media_transfer_failed,
        transferStatus = TransferStatus.FAILED,
        endItem = SenderEndItem.Error,
    ),
    ) {
        override fun isValidNextState(nextState: ChipStateSender): Boolean {
            return nextState == FAR_FROM_RECEIVER ||
                    nextState == ALMOST_CLOSE_TO_END_CAST ||
                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
        }
    },

    /** A state representing that this device is far away from any receiver device. */
    FAR_FROM_RECEIVER(
@@ -162,6 +208,12 @@ enum class ChipStateSender(
            throw IllegalArgumentException("FAR_FROM_RECEIVER should never be displayed, " +
                "so its string should never be fetched")
        }

        override fun isValidNextState(nextState: ChipStateSender): Boolean {
            return nextState == FAR_FROM_RECEIVER ||
                    nextState.transferStatus == TransferStatus.NOT_STARTED ||
                    nextState.transferStatus == TransferStatus.IN_PROGRESS
        }
    };

    /**
@@ -175,6 +227,8 @@ enum class ChipStateSender(
        return Text.Loaded(context.getString(stringResId!!, otherDeviceName))
    }

    abstract fun isValidNextState(nextState: ChipStateSender): Boolean

    companion object {
        /**
         * Returns the sender state enum associated with the given [displayState] from
@@ -197,6 +251,31 @@ enum class ChipStateSender(
         */
        @StatusBarManager.MediaTransferSenderState
        fun getSenderStateIdFromName(name: String): Int = valueOf(name).stateInt

        /**
         * Validates the transition from a chip state to another.
         *
         * @param currentState is the current state of the chip.
         * @param desiredState is the desired state of the chip.
         * @return true if the transition from [currentState] to [desiredState] is valid, and false
         * otherwise.
         */
        fun isValidStateTransition(
                currentState: ChipStateSender?,
                desiredState: ChipStateSender,
        ): Boolean {
            // Far from receiver is the default state.
            if (currentState == null) {
                return FAR_FROM_RECEIVER.isValidNextState(desiredState)
            }

            // No change in state is valid.
            if (currentState == desiredState) {
                return true
            }

            return currentState.isValidNextState(desiredState)
        }
    }
}

+16 −1
Original line number Diff line number Diff line
@@ -52,6 +52,8 @@ constructor(
) : CoreStartable {

    private var displayedState: ChipStateSender? = null
    // A map to store current chip state per id.
    private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf()

    private val commandQueueCallbacks =
        object : CommandQueue.Callbacks {
@@ -87,9 +89,22 @@ constructor(
            logger.logStateChangeError(displayState)
            return
        }

        val currentState = stateMap[routeInfo.id]
        if (!ChipStateSender.isValidStateTransition(currentState, chipState)) {
            // ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state.
            logger.logInvalidStateTransitionError(
                currentState = currentState?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name,
                chipState.name
            )
            return
        }
        uiEventLogger.logSenderStateChange(chipState)

        stateMap.put(routeInfo.id, chipState)
        if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
            // No need to store the state since it is the default state
            stateMap.remove(routeInfo.id)
            // Return early if we're not displaying a chip anyway
            val currentDisplayedState = displayedState ?: return

@@ -119,7 +134,7 @@ constructor(
                    context,
                    logger,
                )
            )
            ) { stateMap.remove(routeInfo.id) }
        }
    }

+6 −2
Original line number Diff line number Diff line
@@ -105,8 +105,9 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
     *
     * This method handles inflating and attaching the view, then delegates to [updateView] to
     * display the correct information in the view.
     * @param onViewTimeout a runnable that runs after the view timeout.
     */
    fun displayView(newInfo: T) {
    fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
        val currentDisplayInfo = displayInfo

        // Update our list of active devices by removing it if necessary, then adding back at the
@@ -173,7 +174,10 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
            cancelViewTimeout?.run()
        }
        cancelViewTimeout = mainExecutor.executeDelayed(
            { removeView(id, REMOVAL_REASON_TIMEOUT) },
            {
                removeView(id, REMOVAL_REASON_TIMEOUT)
                onViewTimeout?.run()
            },
            timeout.toLong()
        )
    }
+216 −8
Original line number Diff line number Diff line
@@ -265,6 +265,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {

    @Test
    fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
        displayReceiverTriggered()
        reset(vibratorHelper)
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
            routeInfo,
@@ -278,13 +280,15 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
            .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
        assertThat(uiEventLoggerFake.eventId(0))
        // Event index 1 since initially displaying the triggered chip would also log an event.
        assertThat(uiEventLoggerFake.eventId(1))
            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
        verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
    }

    @Test
    fun transferToReceiverSucceeded_nullUndoCallback_noUndo() {
        displayReceiverTriggered()
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
            routeInfo,
@@ -297,6 +301,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {

    @Test
    fun transferToReceiverSucceeded_withUndoRunnable_undoVisible() {
        displayReceiverTriggered()
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
            routeInfo,
@@ -313,6 +318,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
    @Test
    fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
        var undoCallbackCalled = false
        displayReceiverTriggered()
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
            routeInfo,
@@ -325,8 +331,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {

        getChipbarView().getUndoButton().performClick()

        // Event index 1 since initially displaying the succeeded chip would also log an event
        assertThat(uiEventLoggerFake.eventId(1))
        // Event index 2 since initially displaying the triggered and succeeded chip would also log
        // events.
        assertThat(uiEventLoggerFake.eventId(2))
            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id)
        assertThat(undoCallbackCalled).isTrue()
        assertThat(getChipbarView().getChipText())
@@ -335,6 +342,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {

    @Test
    fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
        displayThisDeviceTriggered()
        reset(vibratorHelper)
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
            routeInfo,
@@ -348,13 +357,15 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
            .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
        assertThat(uiEventLoggerFake.eventId(0))
        // Event index 1 since initially displaying the triggered chip would also log an event.
        assertThat(uiEventLoggerFake.eventId(1))
            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
        verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
    }

    @Test
    fun transferToThisDeviceSucceeded_nullUndoCallback_noUndo() {
        displayThisDeviceTriggered()
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
            routeInfo,
@@ -367,6 +378,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {

    @Test
    fun transferToThisDeviceSucceeded_withUndoRunnable_undoVisible() {
        displayThisDeviceTriggered()
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
            routeInfo,
@@ -383,6 +395,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
    @Test
    fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
        var undoCallbackCalled = false
        displayThisDeviceTriggered()
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
            routeInfo,
@@ -395,8 +408,9 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {

        getChipbarView().getUndoButton().performClick()

        // Event index 1 since initially displaying the succeeded chip would also log an event
        assertThat(uiEventLoggerFake.eventId(1))
        // Event index 2 since initially displaying the triggered and succeeded chip would also log
        // events.
        assertThat(uiEventLoggerFake.eventId(2))
            .isEqualTo(
                MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
            )
@@ -407,6 +421,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {

    @Test
    fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
        displayReceiverTriggered()
        reset(vibratorHelper)
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
            routeInfo,
@@ -421,13 +437,20 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
        assertThat(uiEventLoggerFake.eventId(0))
        // Event index 1 since initially displaying the triggered chip would also log an event.
        assertThat(uiEventLoggerFake.eventId(1))
            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
        verify(vibratorHelper).vibrate(any<VibrationEffect>())
    }

    @Test
    fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
            routeInfo,
            null
        )
        reset(vibratorHelper)
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
            routeInfo,
@@ -442,7 +465,8 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
        assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
        assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
        assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
        assertThat(uiEventLoggerFake.eventId(0))
        // Event index 1 since initially displaying the triggered chip would also log an event.
        assertThat(uiEventLoggerFake.eventId(1))
            .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
        verify(vibratorHelper).vibrate(any<VibrationEffect>())
    }
@@ -516,6 +540,166 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
        verify(windowManager, never()).addView(any(), any())
    }

    @Test
    fun commandQueueCallback_receiverTriggeredThenAlmostStart_invalidTransitionLogged() {
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
            routeInfo,
            null
        )
        verify(windowManager).addView(any(), any())
        reset(windowManager)

        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
            routeInfo,
            null
        )

        verify(logger).logInvalidStateTransitionError(any(), any())
        verify(windowManager, never()).addView(any(), any())
    }

    @Test
    fun commandQueueCallback_thisDeviceTriggeredThenAlmostEnd_invalidTransitionLogged() {
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
            routeInfo,
            null
        )
        verify(windowManager).addView(any(), any())
        reset(windowManager)

        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
            routeInfo,
            null
        )

        verify(logger).logInvalidStateTransitionError(any(), any())
        verify(windowManager, never()).addView(any(), any())
    }

    @Test
    fun commandQueueCallback_receiverSucceededThenReceiverTriggered_invalidTransitionLogged() {
        displayReceiverTriggered()
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
            routeInfo,
            null
        )
        reset(windowManager)

        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
            routeInfo,
            null
        )

        verify(logger).logInvalidStateTransitionError(any(), any())
        verify(windowManager, never()).addView(any(), any())
    }

    @Test
    fun commandQueueCallback_thisDeviceSucceededThenThisDeviceTriggered_invalidTransitionLogged() {
        displayThisDeviceTriggered()
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
            routeInfo,
            null
        )
        reset(windowManager)

        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
            routeInfo,
            null
        )

        verify(logger).logInvalidStateTransitionError(any(), any())
        verify(windowManager, never()).addView(any(), any())
    }

    @Test
    fun commandQueueCallback_almostStartThenReceiverSucceeded_invalidTransitionLogged() {
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
            routeInfo,
            null
        )
        verify(windowManager).addView(any(), any())
        reset(windowManager)

        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
            routeInfo,
            null
        )

        verify(logger).logInvalidStateTransitionError(any(), any())
        verify(windowManager, never()).addView(any(), any())
    }

    @Test
    fun commandQueueCallback_almostEndThenThisDeviceSucceeded_invalidTransitionLogged() {
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
            routeInfo,
            null
        )
        verify(windowManager).addView(any(), any())
        reset(windowManager)

        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
            routeInfo,
            null
        )

        verify(logger).logInvalidStateTransitionError(any(), any())
        verify(windowManager, never()).addView(any(), any())
    }

    @Test
    fun commandQueueCallback_AlmostStartThenReceiverFailed_invalidTransitionLogged() {
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
            routeInfo,
            null
        )
        verify(windowManager).addView(any(), any())
        reset(windowManager)

        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
            routeInfo,
            null
        )

        verify(logger).logInvalidStateTransitionError(any(), any())
        verify(windowManager, never()).addView(any(), any())
    }

    @Test
    fun commandQueueCallback_almostEndThenThisDeviceFailed_invalidTransitionLogged() {
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
            routeInfo,
            null
        )
        verify(windowManager).addView(any(), any())
        reset(windowManager)

        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
            routeInfo,
            null
        )

        verify(logger).logInvalidStateTransitionError(any(), any())
        verify(windowManager, never()).addView(any(), any())
    }

    @Test
    fun receivesNewStateFromCommandQueue_isLogged() {
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
@@ -575,6 +759,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {

    @Test
    fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
        displayReceiverTriggered()
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
            routeInfo,
@@ -598,6 +783,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {

    @Test
    fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
        displayThisDeviceTriggered()
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
            routeInfo,
@@ -621,6 +807,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {

    @Test
    fun transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
        displayReceiverTriggered()
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
            routeInfo,
@@ -660,6 +847,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {

    @Test
    fun transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
        displayThisDeviceTriggered()
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
            routeInfo,
@@ -717,6 +905,26 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
    private fun ChipStateSender.getExpectedStateText(): String? {
        return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context)
    }

    // display receiver triggered state helper method to make sure we start from a valid state
    // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_RECEIVER_TRIGGERED).
    private fun displayReceiverTriggered() {
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
            routeInfo,
            null
        )
    }

    // display this device triggered state helper method to make sure we start from a valid state
    // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_THIS_DEVICE_TRIGGERED).
    private fun displayThisDeviceTriggered() {
        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
            routeInfo,
            null
        )
    }
}

private const val APP_NAME = "Fake app name"