From babe2cd5f388ca34b87a9e18705e0924406fe862 Mon Sep 17 00:00:00 2001 From: Caitlin Cassidy Date: Thu, 13 Jan 2022 20:53:33 +0000 Subject: [PATCH 1/6] [Media TTT] Update chip states to not require otherDeviceName if they don't need it. Define a #getChipTextString method instead. Bug: 203800643 Bug: 203800347 Test: verify all states still trigger via adb Test: media.taptotransfer tests Change-Id: Iba8a67a0a8affc4857fafdaf1880b920db0a3fe1 --- .../taptotransfer/sender/ChipStateSender.kt | 88 +++++++++---------- .../sender/MediaTttChipControllerSender.kt | 2 +- .../sender/MediaTttSenderService.kt | 7 +- .../MediaTttChipControllerSenderTest.kt | 28 +++--- .../sender/MediaTttSenderServiceTest.kt | 6 +- 5 files changed, 66 insertions(+), 65 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index 1fd3af4ce8d94..0f2715c4a6e35 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -16,8 +16,8 @@ package com.android.systemui.media.taptotransfer.sender +import android.content.Context import android.graphics.drawable.Drawable -import androidx.annotation.StringRes import com.android.systemui.R import com.android.systemui.media.taptotransfer.common.MediaTttChipState @@ -27,91 +27,89 @@ import com.android.systemui.media.taptotransfer.common.MediaTttChipState * * This is a sealed class where each subclass represents a specific chip state. Each subclass can * contain additional information that is necessary for only that state. - * - * @property chipText a string resource for the text that the chip should display. - * @property otherDeviceName the name of the other device involved in the transfer. */ sealed class ChipStateSender( appIconDrawable: Drawable, - appIconContentDescription: String, - @StringRes internal val chipText: Int, - internal val otherDeviceName: String, -) : MediaTttChipState(appIconDrawable, appIconContentDescription) + appIconContentDescription: String +) : MediaTttChipState(appIconDrawable, appIconContentDescription) { + /** Returns a fully-formed string with the text that the chip should display. */ + abstract fun getChipTextString(context: Context): String +} /** * A state representing that the two devices are close but not close enough to *start* a cast to * the receiver device. The chip will instruct the user to move closer in order to initiate the * transfer to the receiver. + * + * @property otherDeviceName the name of the other device involved in the transfer. */ class MoveCloserToStartCast( appIconDrawable: Drawable, appIconContentDescription: String, - otherDeviceName: String, -) : ChipStateSender( - appIconDrawable, - appIconContentDescription, - R.string.media_move_closer_to_start_cast, - otherDeviceName -) + private val otherDeviceName: String, +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_move_closer_to_start_cast, otherDeviceName) + } +} /** * A state representing that the two devices are close but not close enough to *end* a cast that's * currently occurring the receiver device. The chip will instruct the user to move closer in order * to initiate the transfer from the receiver and back onto this device (the original sender). + * + * @property otherDeviceName the name of the other device involved in the transfer. */ class MoveCloserToEndCast( appIconDrawable: Drawable, appIconContentDescription: String, - otherDeviceName: String, -) : ChipStateSender( - appIconDrawable, - appIconContentDescription, - R.string.media_move_closer_to_end_cast, - otherDeviceName -) + private val otherDeviceName: String, +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_move_closer_to_end_cast, otherDeviceName) + } +} /** * A state representing that a transfer to the receiver device has been initiated (but not * completed). + * + * @property otherDeviceName the name of the other device involved in the transfer. */ class TransferToReceiverTriggered( appIconDrawable: Drawable, appIconContentDescription: String, - otherDeviceName: String -) : ChipStateSender( - appIconDrawable, - appIconContentDescription, - R.string.media_transfer_playing, - otherDeviceName -) + private val otherDeviceName: String +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_transfer_playing, otherDeviceName) + } +} /** * A state representing that a transfer has been successfully completed. * + * @property otherDeviceName the name of the other device involved in the transfer. * @property undoRunnable if present, the runnable that should be run to undo the transfer. We will * show an Undo button on the chip if this runnable is present. */ class TransferSucceeded( appIconDrawable: Drawable, appIconContentDescription: String, - otherDeviceName: String, + private val otherDeviceName: String, val undoRunnable: Runnable? = null -) : ChipStateSender(appIconDrawable, - appIconContentDescription, - R.string.media_transfer_playing, - otherDeviceName -) +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_transfer_playing, otherDeviceName) + } +} /** A state representing that a transfer has failed. */ class TransferFailed( appIconDrawable: Drawable, - appIconContentDescription: String, - // TODO(b/211493953): The failed chip doesn't need [otherDeviceName] so we may want to remove - // [otherDeviceName] from the superclass [ChipStateSender]. - otherDeviceName: String, -) : ChipStateSender( - appIconDrawable, - appIconContentDescription, - R.string.media_transfer_failed, - otherDeviceName -) + appIconContentDescription: String +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_transfer_failed) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 84672ab9a31cc..88264b6e669ee 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -45,7 +45,7 @@ class MediaTttChipControllerSender @Inject constructor( // Text currentChipView.requireViewById(R.id.text).apply { - text = context.getString(chipState.chipText, chipState.otherDeviceName) + text = chipState.getChipTextString(context) } // Loading diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt index 0fe324eb557df..cbf646209b007 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt @@ -53,7 +53,7 @@ class MediaTttSenderService @Inject constructor( override fun transferFailed( mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo ) { - this@MediaTttSenderService.transferFailed(mediaInfo, otherDeviceInfo) + this@MediaTttSenderService.transferFailed(mediaInfo) } override fun transferToReceiverTriggered( @@ -91,11 +91,10 @@ class MediaTttSenderService @Inject constructor( controller.displayChip(chipState) } - private fun transferFailed(mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo) { + private fun transferFailed(mediaInfo: MediaRoute2Info) { val chipState = TransferFailed( appIconDrawable = fakeAppIconDrawable, - appIconContentDescription = mediaInfo.name.toString(), - otherDeviceName = otherDeviceInfo.name + appIconContentDescription = mediaInfo.name.toString() ) controller.displayChip(chipState) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index e9ddf3d857b1c..b6c1834e68f1a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -53,12 +53,13 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun moveCloserToStartCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() { - controllerSender.displayChip(moveCloserToStartCast()) + val state = moveCloserToStartCast() + controllerSender.displayChip(state) val chipView = getChipView() assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) - assertThat(chipView.getChipText()).contains(DEVICE_NAME) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) @@ -66,12 +67,13 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun moveCloserToEndCast_appIcon_deviceName_noLoadingIcon_noUndo_noFailureIcon() { - controllerSender.displayChip(moveCloserToEndCast()) + val state = moveCloserToEndCast() + controllerSender.displayChip(state) val chipView = getChipView() assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) - assertThat(chipView.getChipText()).contains(DEVICE_NAME) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) @@ -79,12 +81,13 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun transferToReceiverTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() { - controllerSender.displayChip(transferToReceiverTriggered()) + val state = transferToReceiverTriggered() + controllerSender.displayChip(state) val chipView = getChipView() assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) - assertThat(chipView.getChipText()).contains(DEVICE_NAME) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) @@ -92,12 +95,13 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun transferSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() { - controllerSender.displayChip(transferSucceeded()) + val state = transferSucceeded() + controllerSender.displayChip(state) val chipView = getChipView() assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) - assertThat(chipView.getChipText()).contains(DEVICE_NAME) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) } @@ -132,12 +136,13 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun transferFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() { - controllerSender.displayChip(transferFailed()) + val state = transferFailed() + controllerSender.displayChip(state) val chipView = getChipView() assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) - assertThat(chipView.getChipText()).doesNotContain(DEVICE_NAME) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.VISIBLE) @@ -219,8 +224,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { ) = TransferSucceeded(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoRunnable) /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferFailed() = - TransferFailed(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) + private fun transferFailed() = TransferFailed(appIconDrawable, APP_ICON_CONTENT_DESC) } private const val DEVICE_NAME = "My Tablet" diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt index ca909450c1445..8a491c220592b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt @@ -44,7 +44,7 @@ class MediaTttSenderServiceTest : SysuiTestCase() { verify(controller).displayChip(capture(chipStateCaptor)) val chipState = chipStateCaptor.value!! - assertThat(chipState.otherDeviceName).isEqualTo(name) + assertThat(chipState.getChipTextString(context)).contains(name) } @Test @@ -56,7 +56,7 @@ class MediaTttSenderServiceTest : SysuiTestCase() { verify(controller).displayChip(capture(chipStateCaptor)) val chipState = chipStateCaptor.value!! - assertThat(chipState.otherDeviceName).isEqualTo(name) + assertThat(chipState.getChipTextString(context)).contains(name) } @Test @@ -68,7 +68,7 @@ class MediaTttSenderServiceTest : SysuiTestCase() { verify(controller).displayChip(capture(chipStateCaptor)) val chipState = chipStateCaptor.value!! - assertThat(chipState.otherDeviceName).isEqualTo(name) + assertThat(chipState.getChipTextString(context)).contains(name) } @Test From b3defd20a441e81a15d47e78d1f3ce3bc42a6025 Mon Sep 17 00:00:00 2001 From: Caitlin Cassidy Date: Thu, 13 Jan 2022 20:37:59 +0000 Subject: [PATCH 2/6] [Media TTT] Add transferToThisDeviceTriggered callback. Bug: 203800643 Bug: 203800347 Test: `adb shell cmd statusbar media-ttt-chip-add-sender Device TransferToThisDeviceTriggered` triggers a chip saying "Playing on this device" with loading icon Test: media.taptotransfer tests Change-Id: Iddc0593603f7fdd96a67b805a808205e77658d8d --- packages/SystemUI/res/values/strings.xml | 4 ++- .../mediattt/IDeviceSenderCallback.aidl | 12 +++++++ .../MediaTttCommandLineHelper.kt | 10 ++++++ .../taptotransfer/sender/ChipStateSender.kt | 32 +++++++++++++++++-- .../sender/MediaTttChipControllerSender.kt | 3 +- .../sender/MediaTttSenderService.kt | 14 ++++++++ .../MediaTttCommandLineHelperTest.kt | 15 +++++++++ .../MediaTttChipControllerSenderTest.kt | 18 +++++++++++ .../sender/MediaTttSenderServiceTest.kt | 7 ++++ 9 files changed, 110 insertions(+), 5 deletions(-) diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3a9159094433b..078356f4ef122 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2192,7 +2192,9 @@ Move closer to %1$s to play here - Playing on %1$s + Playing on %1$s + + Playing on this phone Something went wrong diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl index 8db3e9d135a06..eb026e2187c46 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl @@ -72,6 +72,18 @@ interface IDeviceSenderCallback { oneway void transferToReceiverTriggered( in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); + /** + * Invoke to notify System UI that a media transfer from the receiver and back to this device + * (the sender) has been started. + * + * Important notes: + * - This callback is for *ending* a cast. It should be used when media is currently being + * played on the receiver device and the media has started being transferred to play locally + * instead. + */ + oneway void transferToThisDeviceTriggered( + in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); + /** * Invoke to notify System UI that the attempted transfer has failed. * diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index e8a847f6ae418..5ce9d56c15375 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -36,6 +36,7 @@ import com.android.systemui.media.taptotransfer.sender.MoveCloserToEndCast import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast import com.android.systemui.media.taptotransfer.sender.TransferFailed import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTriggered +import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceTriggered import com.android.systemui.media.taptotransfer.sender.TransferSucceeded import com.android.systemui.shared.mediattt.DeviceInfo import com.android.systemui.shared.mediattt.IDeviceSenderCallback @@ -98,6 +99,11 @@ class MediaTttCommandLineHelper @Inject constructor( senderCallback.transferToReceiverTriggered(mediaInfo, otherDeviceInfo) } } + TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME -> { + runOnService { senderCallback -> + senderCallback.transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo) + } + } // TODO(b/203800643): Migrate this command to invoke the service instead of the // controller. TRANSFER_SUCCEEDED_COMMAND_NAME -> { @@ -120,6 +126,7 @@ class MediaTttCommandLineHelper @Inject constructor( "$MOVE_CLOSER_TO_START_CAST_COMMAND_NAME, " + "$MOVE_CLOSER_TO_END_CAST_COMMAND_NAME, " + "$TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME, " + + "$TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME, " + "$TRANSFER_SUCCEEDED_COMMAND_NAME, " + TRANSFER_FAILED_COMMAND_NAME ) @@ -231,6 +238,9 @@ val MOVE_CLOSER_TO_END_CAST_COMMAND_NAME = MoveCloserToEndCast::class.simpleName @VisibleForTesting val TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME = TransferToReceiverTriggered::class.simpleName!! @VisibleForTesting +val TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME = + TransferToThisDeviceTriggered::class.simpleName!! +@VisibleForTesting val TRANSFER_SUCCEEDED_COMMAND_NAME = TransferSucceeded::class.simpleName!! @VisibleForTesting val TRANSFER_FAILED_COMMAND_NAME = TransferFailed::class.simpleName!! diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index 0f2715c4a6e35..abfd9d3e498af 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -34,6 +34,9 @@ sealed class ChipStateSender( ) : MediaTttChipState(appIconDrawable, appIconContentDescription) { /** Returns a fully-formed string with the text that the chip should display. */ abstract fun getChipTextString(context: Context): String + + /** Returns true if the loading icon should be displayed and false otherwise. */ + abstract fun showLoading(): Boolean } /** @@ -51,6 +54,8 @@ class MoveCloserToStartCast( override fun getChipTextString(context: Context): String { return context.getString(R.string.media_move_closer_to_start_cast, otherDeviceName) } + + override fun showLoading() = false } /** @@ -68,6 +73,8 @@ class MoveCloserToEndCast( override fun getChipTextString(context: Context): String { return context.getString(R.string.media_move_closer_to_end_cast, otherDeviceName) } + + override fun showLoading() = false } /** @@ -82,8 +89,25 @@ class TransferToReceiverTriggered( private val otherDeviceName: String ) : ChipStateSender(appIconDrawable, appIconContentDescription) { override fun getChipTextString(context: Context): String { - return context.getString(R.string.media_transfer_playing, otherDeviceName) + return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName) } + + override fun showLoading() = true +} + +/** + * A state representing that a transfer from the receiver device and back to this device (the + * sender) has been initiated (but not completed). + */ +class TransferToThisDeviceTriggered( + appIconDrawable: Drawable, + appIconContentDescription: String +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_transfer_playing_this_device) + } + + override fun showLoading() = true } /** @@ -100,8 +124,10 @@ class TransferSucceeded( val undoRunnable: Runnable? = null ) : ChipStateSender(appIconDrawable, appIconContentDescription) { override fun getChipTextString(context: Context): String { - return context.getString(R.string.media_transfer_playing, otherDeviceName) + return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName) } + + override fun showLoading() = false } /** A state representing that a transfer has failed. */ @@ -112,4 +138,6 @@ class TransferFailed( override fun getChipTextString(context: Context): String { return context.getString(R.string.media_transfer_failed) } + + override fun showLoading() = false } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 88264b6e669ee..5cfd674b21cb7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -49,9 +49,8 @@ class MediaTttChipControllerSender @Inject constructor( } // Loading - val showLoading = chipState is TransferToReceiverTriggered currentChipView.requireViewById(R.id.loading).visibility = - if (showLoading) { View.VISIBLE } else { View.GONE } + if (chipState.showLoading()) { View.VISIBLE } else { View.GONE } // Undo val undoClickListener: View.OnClickListener? = diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt index cbf646209b007..56f33a8dcf755 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt @@ -61,6 +61,12 @@ class MediaTttSenderService @Inject constructor( ) { this@MediaTttSenderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo) } + + override fun transferToThisDeviceTriggered( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo + ) { + this@MediaTttSenderService.transferToThisDeviceTriggered(mediaInfo) + } } // TODO(b/203800643): Use the app icon from the media info instead of a fake one. @@ -109,4 +115,12 @@ class MediaTttSenderService @Inject constructor( ) controller.displayChip(chipState) } + + private fun transferToThisDeviceTriggered(mediaInfo: MediaRoute2Info) { + val chipState = TransferToThisDeviceTriggered( + appIconDrawable = fakeAppIconDrawable, + appIconContentDescription = mediaInfo.name.toString() + ) + controller.displayChip(chipState) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index cf5d47747e4c4..6275c240921a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -145,6 +145,14 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) } + @Test + fun sender_transferToThisDeviceTriggered_chipDisplayWithCorrectState() { + commandRegistry.onShellCommand(pw, getTransferToThisDeviceTriggeredCommand()) + + assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() + verify(mediaSenderService).transferToThisDeviceTriggered(any(), any()) + } + @Test fun sender_transferSucceeded_chipDisplayWithCorrectState() { commandRegistry.onShellCommand(pw, getTransferSucceededCommand()) @@ -202,6 +210,13 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME ) + private fun getTransferToThisDeviceTriggeredCommand(): Array = + arrayOf( + ADD_CHIP_COMMAND_SENDER_TAG, + DEVICE_NAME, + TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME + ) + private fun getTransferSucceededCommand(): Array = arrayOf( ADD_CHIP_COMMAND_SENDER_TAG, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index b6c1834e68f1a..703f6b0d57458 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -93,6 +93,20 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) } + @Test + fun transferToThisDeviceTriggered_appIcon_loadingIcon_noUndo_noFailureIcon() { + val state = transferToThisDeviceTriggered() + controllerSender.displayChip(state) + + val chipView = getChipView() + assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) + assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE) + assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) + assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) + } + @Test fun transferSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() { val state = transferSucceeded() @@ -218,6 +232,10 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { private fun transferToReceiverTriggered() = TransferToReceiverTriggered(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) + /** Helper method providing default parameters to not clutter up the tests. */ + private fun transferToThisDeviceTriggered() = + TransferToThisDeviceTriggered(appIconDrawable, APP_ICON_CONTENT_DESC) + /** Helper method providing default parameters to not clutter up the tests. */ private fun transferSucceeded( undoRunnable: Runnable? = null diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt index 8a491c220592b..1abf49f2c4bf6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt @@ -59,6 +59,13 @@ class MediaTttSenderServiceTest : SysuiTestCase() { assertThat(chipState.getChipTextString(context)).contains(name) } + @Test + fun transferToThisDeviceTriggered_controllerTriggeredWithCorrectState() { + callback.transferToThisDeviceTriggered(mediaInfo, DeviceInfo("Fake name")) + + verify(controller).displayChip(any()) + } + @Test fun transferToReceiverTriggered_controllerTriggeredWithCorrectState() { val name = "Fake name" From a6d18d89f953644ec0f96062214a1bc8bd970ece Mon Sep 17 00:00:00 2001 From: Caitlin Cassidy Date: Fri, 14 Jan 2022 19:05:23 +0000 Subject: [PATCH 3/6] [Media TTT] Add the #transferToReceiverSucceeded callback. Bug: 203800643 Bug: 203800347 Test: verify `adb shell cmd statusbar media-ttt-chip-add-sender Device TransferToReceiverSucceeded` shows the chip Test: media.taptotransfer tests Change-Id: Id5aef5c4f5fe61d26ad556b28f6d6cdb4d6ad31d --- .../mediattt/IDeviceSenderCallback.aidl | 12 ++++++++ .../MediaTttCommandLineHelper.kt | 25 +++++----------- .../taptotransfer/sender/ChipStateSender.kt | 4 +-- .../sender/MediaTttChipControllerSender.kt | 2 +- .../sender/MediaTttSenderService.kt | 19 ++++++++++++ .../MediaTttCommandLineHelperTest.kt | 14 +++++---- .../MediaTttChipControllerSenderTest.kt | 29 ++++++++++--------- .../sender/MediaTttSenderServiceTest.kt | 12 ++++++++ 8 files changed, 77 insertions(+), 40 deletions(-) diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl index eb026e2187c46..4e230c3517f48 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl @@ -84,6 +84,18 @@ interface IDeviceSenderCallback { oneway void transferToThisDeviceTriggered( in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); + /** + * Invoke to notify System UI that a media transfer from this device (the sender) to a receiver + * device has finished successfully. + * + * Important notes: + * - This callback is for *starting* a cast. It should be used when this device had previously + * been playing media locally and the media has successfully been transferred to the + * receiver device instead. + */ + oneway void transferToReceiverSucceeded( + in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); + /** * Invoke to notify System UI that the attempted transfer has failed. * diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index 5ce9d56c15375..5c12f6800dd4f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -37,7 +37,7 @@ import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast import com.android.systemui.media.taptotransfer.sender.TransferFailed import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTriggered import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceTriggered -import com.android.systemui.media.taptotransfer.sender.TransferSucceeded +import com.android.systemui.media.taptotransfer.sender.TransferToReceiverSucceeded import com.android.systemui.shared.mediattt.DeviceInfo import com.android.systemui.shared.mediattt.IDeviceSenderCallback import com.android.systemui.statusbar.commandline.Command @@ -104,17 +104,10 @@ class MediaTttCommandLineHelper @Inject constructor( senderCallback.transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo) } } - // TODO(b/203800643): Migrate this command to invoke the service instead of the - // controller. - TRANSFER_SUCCEEDED_COMMAND_NAME -> { - mediaTttChipControllerSender.displayChip( - TransferSucceeded( - appIconDrawable, - APP_ICON_CONTENT_DESCRIPTION, - otherDeviceName, - fakeUndoRunnable - ) - ) + TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME -> { + runOnService { senderCallback -> + senderCallback.transferToReceiverSucceeded(mediaInfo, otherDeviceInfo) + } } TRANSFER_FAILED_COMMAND_NAME -> { runOnService { senderCallback -> @@ -127,7 +120,7 @@ class MediaTttCommandLineHelper @Inject constructor( "$MOVE_CLOSER_TO_END_CAST_COMMAND_NAME, " + "$TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME, " + "$TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME, " + - "$TRANSFER_SUCCEEDED_COMMAND_NAME, " + + "$TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME, " + TRANSFER_FAILED_COMMAND_NAME ) } @@ -217,10 +210,6 @@ class MediaTttCommandLineHelper @Inject constructor( /** Runs the command on the provided [senderCallback]. */ fun run(senderCallback: IDeviceSenderCallback) } - - private val fakeUndoRunnable = Runnable { - Log.i(TAG, "Undo runnable triggered") - } } @VisibleForTesting @@ -241,7 +230,7 @@ val TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME = TransferToReceiverTriggered::c val TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME = TransferToThisDeviceTriggered::class.simpleName!! @VisibleForTesting -val TRANSFER_SUCCEEDED_COMMAND_NAME = TransferSucceeded::class.simpleName!! +val TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME = TransferToReceiverSucceeded::class.simpleName!! @VisibleForTesting val TRANSFER_FAILED_COMMAND_NAME = TransferFailed::class.simpleName!! diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index abfd9d3e498af..2edbe272b02cc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -111,13 +111,13 @@ class TransferToThisDeviceTriggered( } /** - * A state representing that a transfer has been successfully completed. + * A state representing that a transfer to the receiver device has been successfully completed. * * @property otherDeviceName the name of the other device involved in the transfer. * @property undoRunnable if present, the runnable that should be run to undo the transfer. We will * show an Undo button on the chip if this runnable is present. */ -class TransferSucceeded( +class TransferToReceiverSucceeded( appIconDrawable: Drawable, appIconContentDescription: String, private val otherDeviceName: String, diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 5cfd674b21cb7..e400530f15337 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -54,7 +54,7 @@ class MediaTttChipControllerSender @Inject constructor( // Undo val undoClickListener: View.OnClickListener? = - if (chipState is TransferSucceeded && chipState.undoRunnable != null) + if (chipState is TransferToReceiverSucceeded && chipState.undoRunnable != null) View.OnClickListener { chipState.undoRunnable.run() } else null diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt index 56f33a8dcf755..9473e4c6af5f5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt @@ -67,6 +67,12 @@ class MediaTttSenderService @Inject constructor( ) { this@MediaTttSenderService.transferToThisDeviceTriggered(mediaInfo) } + + override fun transferToReceiverSucceeded( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo + ) { + this@MediaTttSenderService.transferToReceiverSucceeded(mediaInfo, otherDeviceInfo) + } } // TODO(b/203800643): Use the app icon from the media info instead of a fake one. @@ -123,4 +129,17 @@ class MediaTttSenderService @Inject constructor( ) controller.displayChip(chipState) } + + private fun transferToReceiverSucceeded( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo + ) { + val chipState = TransferToReceiverSucceeded( + appIconDrawable = fakeAppIconDrawable, + appIconContentDescription = mediaInfo.name.toString(), + otherDeviceName = otherDeviceInfo.name + // TODO(b/203800643): Implement the undo API correctly so we can provide an undo + // callback here. + ) + controller.displayChip(chipState) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index 6275c240921a1..7aa9bce158e9e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -154,10 +154,14 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { } @Test - fun sender_transferSucceeded_chipDisplayWithCorrectState() { - commandRegistry.onShellCommand(pw, getTransferSucceededCommand()) + fun sender_transferToReceiverSucceeded_chipDisplayWithCorrectState() { + commandRegistry.onShellCommand(pw, getTransferToReceiverSucceededCommand()) - verify(mediaTttChipControllerSender).displayChip(any(TransferSucceeded::class.java)) + assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() + + val deviceInfoCaptor = argumentCaptor() + verify(mediaSenderService).transferToReceiverSucceeded(any(), capture(deviceInfoCaptor)) + assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) } @Test @@ -217,11 +221,11 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME ) - private fun getTransferSucceededCommand(): Array = + private fun getTransferToReceiverSucceededCommand(): Array = arrayOf( ADD_CHIP_COMMAND_SENDER_TAG, DEVICE_NAME, - TRANSFER_SUCCEEDED_COMMAND_NAME + TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME ) private fun getTransferFailedCommand(): Array = diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index 703f6b0d57458..1682b1d730e7a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -108,8 +108,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } @Test - fun transferSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() { - val state = transferSucceeded() + fun transferToReceiverSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() { + val state = transferToReceiverSucceeded() controllerSender.displayChip(state) val chipView = getChipView() @@ -121,16 +121,16 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } @Test - fun transferSucceededNullUndoRunnable_noUndo() { - controllerSender.displayChip(transferSucceeded(undoRunnable = null)) + fun transferToReceiverSucceeded_nullUndoRunnable_noUndo() { + controllerSender.displayChip(transferToReceiverSucceeded(undoRunnable = null)) val chipView = getChipView() assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) } @Test - fun transferSucceededWithUndoRunnable_undoWithClick() { - controllerSender.displayChip(transferSucceeded { }) + fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() { + controllerSender.displayChip(transferToReceiverSucceeded { }) val chipView = getChipView() assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE) @@ -138,11 +138,11 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } @Test - fun transferSucceededWithUndoRunnable_undoButtonClickRunsRunnable() { + fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() { var runnableRun = false val runnable = Runnable { runnableRun = true } - controllerSender.displayChip(transferSucceeded(undoRunnable = runnable)) + controllerSender.displayChip(transferToReceiverSucceeded(undoRunnable = runnable)) getChipView().getUndoButton().performClick() assertThat(runnableRun).isTrue() @@ -173,7 +173,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun changeFromTransferTriggeredToTransferSucceeded_loadingIconDisappears() { controllerSender.displayChip(transferToReceiverTriggered()) - controllerSender.displayChip(transferSucceeded()) + controllerSender.displayChip(transferToReceiverSucceeded()) assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE) } @@ -181,14 +181,14 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun changeFromTransferTriggeredToTransferSucceeded_undoButtonAppears() { controllerSender.displayChip(transferToReceiverTriggered()) - controllerSender.displayChip(transferSucceeded { }) + controllerSender.displayChip(transferToReceiverSucceeded { }) assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE) } @Test fun changeFromTransferSucceededToMoveCloserToStart_undoButtonDisappears() { - controllerSender.displayChip(transferSucceeded()) + controllerSender.displayChip(transferToReceiverSucceeded()) controllerSender.displayChip(moveCloserToStartCast()) assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE) @@ -237,9 +237,10 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { TransferToThisDeviceTriggered(appIconDrawable, APP_ICON_CONTENT_DESC) /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferSucceeded( - undoRunnable: Runnable? = null - ) = TransferSucceeded(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoRunnable) + private fun transferToReceiverSucceeded(undoRunnable: Runnable? = null) = + TransferToReceiverSucceeded( + appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoRunnable + ) /** Helper method providing default parameters to not clutter up the tests. */ private fun transferFailed() = TransferFailed(appIconDrawable, APP_ICON_CONTENT_DESC) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt index 1abf49f2c4bf6..36c3a884cd460 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt @@ -78,6 +78,18 @@ class MediaTttSenderServiceTest : SysuiTestCase() { assertThat(chipState.getChipTextString(context)).contains(name) } + @Test + fun transferToReceiverSucceeded_controllerTriggeredWithCorrectState() { + val name = "Fake name" + callback.transferToReceiverSucceeded(mediaInfo, DeviceInfo(name)) + + val chipStateCaptor = argumentCaptor() + verify(controller).displayChip(capture(chipStateCaptor)) + + val chipState = chipStateCaptor.value!! + assertThat(chipState.getChipTextString(context)).contains(name) + } + @Test fun transferFailed_controllerTriggeredWithTransferFailedState() { callback.transferFailed(mediaInfo, DeviceInfo("Fake name")) From 5852540a88e40e3257bdd82dca2abaddb16b6d75 Mon Sep 17 00:00:00 2001 From: Caitlin Cassidy Date: Fri, 14 Jan 2022 20:06:51 +0000 Subject: [PATCH 4/6] [Media TTT] Add an undo callback to the interface for when a transfer has succeeded. The callback will be invoked when the user presses the undo button. Bug: 203800643 Bug: 203800347 Test: verify `adb shell cmd statusbar media-ttt-chip-add-sender Device TransferToReceiverSucceeded` shows a chip with an Undo button. Verify tapping the undo button switches the chip to the loading state again. Test: media.taptotransfer tests Change-Id: I912caa4408badffaa7a8e19f04e89bafd5d9a8ad --- ...allback.aidl => IDeviceSenderService.aidl} | 11 ++- .../mediattt/IUndoTransferCallback.aidl | 32 +++++++++ .../MediaTttCommandLineHelper.kt | 71 +++++++++++-------- .../taptotransfer/sender/ChipStateSender.kt | 7 +- .../sender/MediaTttChipControllerSender.kt | 16 ++++- .../sender/MediaTttSenderService.kt | 20 +++--- .../MediaTttCommandLineHelperTest.kt | 10 +-- .../MediaTttChipControllerSenderTest.kt | 45 +++++++++--- .../sender/MediaTttSenderServiceTest.kt | 26 ++++--- 9 files changed, 169 insertions(+), 69 deletions(-) rename packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/{IDeviceSenderCallback.aidl => IDeviceSenderService.aidl} (91%) create mode 100644 packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl similarity index 91% rename from packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl rename to packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl index 4e230c3517f48..3b9950930cbec 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderCallback.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl @@ -18,16 +18,17 @@ package com.android.systemui.shared.mediattt; import android.media.MediaRoute2Info; import com.android.systemui.shared.mediattt.DeviceInfo; +import com.android.systemui.shared.mediattt.IUndoTransferCallback; /** - * A callback interface that can be invoked to trigger media transfer events on System UI. + * An interface that can be invoked to trigger media transfer events on System UI. * * This interface is for the *sender* device, which is the device currently playing media. This * sender device can transfer the media to a different device, called the receiver. * * System UI will implement this interface and other services will invoke it. */ -interface IDeviceSenderCallback { +interface IDeviceSenderService { /** * Invoke to notify System UI that this device (the sender) is close to a receiver device, so * the user can potentially *start* a cast to the receiver device if the user moves their device @@ -92,9 +93,13 @@ interface IDeviceSenderCallback { * - This callback is for *starting* a cast. It should be used when this device had previously * been playing media locally and the media has successfully been transferred to the * receiver device instead. + * + * @param undoCallback will be invoked if the user chooses to undo this transfer. */ oneway void transferToReceiverSucceeded( - in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); + in MediaRoute2Info mediaInfo, + in DeviceInfo otherDeviceInfo, + in IUndoTransferCallback undoCallback); /** * Invoke to notify System UI that the attempted transfer has failed. diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl new file mode 100644 index 0000000000000..b47be8736d233 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IUndoTransferCallback.aidl @@ -0,0 +1,32 @@ +/* + * 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.shared.mediattt; + +/** + * An interface that will be invoked by System UI if the user choose to undo a transfer. + * + * Other services will implement this interface and System UI will invoke it. + */ +interface IUndoTransferCallback { + + /** + * Invoked by SystemUI when the user requests to undo the media transfer that just occurred. + * + * Implementors of this method are repsonsible for actually undoing the transfer. + */ + oneway void onUndoTriggered(); +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index 5c12f6800dd4f..a2ab281b7ab1b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -39,7 +39,8 @@ import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTrigger import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceTriggered import com.android.systemui.media.taptotransfer.sender.TransferToReceiverSucceeded import com.android.systemui.shared.mediattt.DeviceInfo -import com.android.systemui.shared.mediattt.IDeviceSenderCallback +import com.android.systemui.shared.mediattt.IDeviceSenderService +import com.android.systemui.shared.mediattt.IUndoTransferCallback import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import java.io.PrintWriter @@ -56,7 +57,7 @@ class MediaTttCommandLineHelper @Inject constructor( private val mediaTttChipControllerSender: MediaTttChipControllerSender, private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver, ) { - private var senderCallback: IDeviceSenderCallback? = null + private var senderService: IDeviceSenderService? = null private val senderServiceConnection = SenderServiceConnection() private val appIconDrawable = @@ -85,33 +86,45 @@ class MediaTttCommandLineHelper @Inject constructor( when (args[1]) { MOVE_CLOSER_TO_START_CAST_COMMAND_NAME -> { - runOnService { senderCallback -> - senderCallback.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo) + runOnService { senderService -> + senderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo) } } MOVE_CLOSER_TO_END_CAST_COMMAND_NAME -> { - runOnService { senderCallback -> - senderCallback.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo) + runOnService { senderService -> + senderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo) } } TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME -> { - runOnService { senderCallback -> - senderCallback.transferToReceiverTriggered(mediaInfo, otherDeviceInfo) + runOnService { senderService -> + senderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo) } } TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME -> { - runOnService { senderCallback -> - senderCallback.transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo) + runOnService { senderService -> + senderService.transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo) } } TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME -> { - runOnService { senderCallback -> - senderCallback.transferToReceiverSucceeded(mediaInfo, otherDeviceInfo) + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() { + Log.i(TAG, "Undo callback triggered") + // The external services that implement this callback would kick off a + // transfer back to this device, so mimic that here. + runOnService { senderService -> + senderService + .transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo) + } + } + } + runOnService { senderService -> + senderService + .transferToReceiverSucceeded(mediaInfo, otherDeviceInfo, undoCallback) } } TRANSFER_FAILED_COMMAND_NAME -> { - runOnService { senderCallback -> - senderCallback.transferFailed(mediaInfo, otherDeviceInfo) + runOnService { senderService -> + senderService.transferFailed(mediaInfo, otherDeviceInfo) } } else -> { @@ -133,16 +146,16 @@ class MediaTttCommandLineHelper @Inject constructor( ) } - private fun runOnService(command: SenderCallbackCommand) { - val currentServiceCallback = senderCallback - if (currentServiceCallback != null) { - command.run(currentServiceCallback) + private fun runOnService(command: SenderServiceCommand) { + val currentService = senderService + if (currentService != null) { + command.run(currentService) } else { bindService(command) } } - private fun bindService(command: SenderCallbackCommand) { + private fun bindService(command: SenderServiceCommand) { senderServiceConnection.pendingCommand = command val binding = context.bindService( Intent(context, MediaTttSenderService::class.java), @@ -157,7 +170,7 @@ class MediaTttCommandLineHelper @Inject constructor( inner class RemoveChipCommandSender : Command { override fun execute(pw: PrintWriter, args: List) { mediaTttChipControllerSender.removeChip() - if (senderCallback != null) { + if (senderService != null) { context.unbindService(senderServiceConnection) } } @@ -188,27 +201,27 @@ class MediaTttCommandLineHelper @Inject constructor( } } - /** A service connection for [IDeviceSenderCallback]. */ + /** A service connection for [IDeviceSenderService]. */ private inner class SenderServiceConnection : ServiceConnection { // A command that should be run when the service gets connected. - var pendingCommand: SenderCallbackCommand? = null + var pendingCommand: SenderServiceCommand? = null override fun onServiceConnected(className: ComponentName, service: IBinder) { - val newCallback = IDeviceSenderCallback.Stub.asInterface(service) - senderCallback = newCallback + val newCallback = IDeviceSenderService.Stub.asInterface(service) + senderService = newCallback pendingCommand?.run(newCallback) pendingCommand = null } override fun onServiceDisconnected(className: ComponentName) { - senderCallback = null + senderService = null } } - /** An interface defining a command that should be run on the sender callback. */ - private fun interface SenderCallbackCommand { - /** Runs the command on the provided [senderCallback]. */ - fun run(senderCallback: IDeviceSenderCallback) + /** An interface defining a command that should be run on the sender service. */ + private fun interface SenderServiceCommand { + /** Runs the command on the provided [senderService]. */ + fun run(senderService: IDeviceSenderService) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index 2edbe272b02cc..47969f2efcf12 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -20,6 +20,7 @@ import android.content.Context import android.graphics.drawable.Drawable import com.android.systemui.R import com.android.systemui.media.taptotransfer.common.MediaTttChipState +import com.android.systemui.shared.mediattt.IUndoTransferCallback /** * A class that stores all the information necessary to display the media tap-to-transfer chip on @@ -114,14 +115,14 @@ class TransferToThisDeviceTriggered( * A state representing that a transfer to the receiver device has been successfully completed. * * @property otherDeviceName the name of the other device involved in the transfer. - * @property undoRunnable if present, the runnable that should be run to undo the transfer. We will - * show an Undo button on the chip if this runnable is present. + * @property undoCallback if present, the callback that should be called when the user clicks the + * undo button. The undo button will only be shown if this is non-null. */ class TransferToReceiverSucceeded( appIconDrawable: Drawable, appIconContentDescription: String, private val otherDeviceName: String, - val undoRunnable: Runnable? = null + val undoCallback: IUndoTransferCallback? = null ) : ChipStateSender(appIconDrawable, appIconContentDescription) { override fun getChipTextString(context: Context): String { return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index e400530f15337..30a2809f3c42d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -54,8 +54,20 @@ class MediaTttChipControllerSender @Inject constructor( // Undo val undoClickListener: View.OnClickListener? = - if (chipState is TransferToReceiverSucceeded && chipState.undoRunnable != null) - View.OnClickListener { chipState.undoRunnable.run() } + if (chipState is TransferToReceiverSucceeded && chipState.undoCallback != null) + View.OnClickListener { + chipState.undoCallback.onUndoTriggered() + // The external service should eventually send us a + // TransferToThisDeviceTriggered state, but that may take too long to go through + // the binder and the user may be confused as to why the UI hasn't changed yet. + // So, we immediately change the UI here. + displayChip( + TransferToThisDeviceTriggered( + chipState.appIconDrawable, + chipState.appIconContentDescription + ) + ) + } else null val undoView = currentChipView.requireViewById(R.id.undo) diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt index 9473e4c6af5f5..8431e2598eef1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt @@ -25,7 +25,8 @@ import android.media.MediaRoute2Info import android.os.IBinder import com.android.systemui.R import com.android.systemui.shared.mediattt.DeviceInfo -import com.android.systemui.shared.mediattt.IDeviceSenderCallback +import com.android.systemui.shared.mediattt.IUndoTransferCallback +import com.android.systemui.shared.mediattt.IDeviceSenderService import javax.inject.Inject /** @@ -37,7 +38,7 @@ class MediaTttSenderService @Inject constructor( ) : Service() { // TODO(b/203800643): Add logging when callbacks trigger. - private val binder: IBinder = object : IDeviceSenderCallback.Stub() { + private val binder: IBinder = object : IDeviceSenderService.Stub() { override fun closeToReceiverToStartCast( mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo ) { @@ -69,9 +70,13 @@ class MediaTttSenderService @Inject constructor( } override fun transferToReceiverSucceeded( - mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo + mediaInfo: MediaRoute2Info, + otherDeviceInfo: DeviceInfo, + undoCallback: IUndoTransferCallback ) { - this@MediaTttSenderService.transferToReceiverSucceeded(mediaInfo, otherDeviceInfo) + this@MediaTttSenderService.transferToReceiverSucceeded( + mediaInfo, otherDeviceInfo, undoCallback + ) } } @@ -131,14 +136,13 @@ class MediaTttSenderService @Inject constructor( } private fun transferToReceiverSucceeded( - mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback ) { val chipState = TransferToReceiverSucceeded( appIconDrawable = fakeAppIconDrawable, appIconContentDescription = mediaInfo.name.toString(), - otherDeviceName = otherDeviceInfo.name - // TODO(b/203800643): Implement the undo API correctly so we can provide an undo - // callback here. + otherDeviceName = otherDeviceInfo.name, + undoCallback = undoCallback ) controller.displayChip(chipState) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index 7aa9bce158e9e..f2e224705e59c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -21,9 +21,10 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver -import com.android.systemui.media.taptotransfer.sender.* +import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender +import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService import com.android.systemui.shared.mediattt.DeviceInfo -import com.android.systemui.shared.mediattt.IDeviceSenderCallback +import com.android.systemui.shared.mediattt.IDeviceSenderService import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.util.mockito.any @@ -55,7 +56,7 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { @Mock private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver @Mock - private lateinit var mediaSenderService: IDeviceSenderCallback.Stub + private lateinit var mediaSenderService: IDeviceSenderService.Stub private lateinit var mediaSenderServiceComponentName: ComponentName @Before @@ -160,7 +161,8 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() val deviceInfoCaptor = argumentCaptor() - verify(mediaSenderService).transferToReceiverSucceeded(any(), capture(deviceInfoCaptor)) + verify(mediaSenderService) + .transferToReceiverSucceeded(any(), capture(deviceInfoCaptor), any()) assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index 1682b1d730e7a..319b692d004cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -26,6 +26,7 @@ import android.widget.TextView import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.mediattt.IUndoTransferCallback import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -122,7 +123,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun transferToReceiverSucceeded_nullUndoRunnable_noUndo() { - controllerSender.displayChip(transferToReceiverSucceeded(undoRunnable = null)) + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback = null)) val chipView = getChipView() assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) @@ -130,7 +131,10 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() { - controllerSender.displayChip(transferToReceiverSucceeded { }) + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback)) val chipView = getChipView() assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE) @@ -139,13 +143,30 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() { - var runnableRun = false - val runnable = Runnable { runnableRun = true } + var undoCallbackCalled = false + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() { + undoCallbackCalled = true + } + } - controllerSender.displayChip(transferToReceiverSucceeded(undoRunnable = runnable)) + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback)) getChipView().getUndoButton().performClick() - assertThat(runnableRun).isTrue() + assertThat(undoCallbackCalled).isTrue() + } + + @Test + fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback)) + + getChipView().getUndoButton().performClick() + + assertThat(getChipView().getChipText()) + .isEqualTo(transferToThisDeviceTriggered().getChipTextString(context)) } @Test @@ -181,7 +202,13 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun changeFromTransferTriggeredToTransferSucceeded_undoButtonAppears() { controllerSender.displayChip(transferToReceiverTriggered()) - controllerSender.displayChip(transferToReceiverSucceeded { }) + controllerSender.displayChip( + transferToReceiverSucceeded( + object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + ) + ) assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE) } @@ -237,9 +264,9 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { TransferToThisDeviceTriggered(appIconDrawable, APP_ICON_CONTENT_DESC) /** Helper method providing default parameters to not clutter up the tests. */ - private fun transferToReceiverSucceeded(undoRunnable: Runnable? = null) = + private fun transferToReceiverSucceeded(undoCallback: IUndoTransferCallback? = null) = TransferToReceiverSucceeded( - appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoRunnable + appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback ) /** Helper method providing default parameters to not clutter up the tests. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt index 36c3a884cd460..177a1c275fba7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt @@ -4,7 +4,8 @@ import android.media.MediaRoute2Info import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.shared.mediattt.DeviceInfo -import com.android.systemui.shared.mediattt.IDeviceSenderCallback +import com.android.systemui.shared.mediattt.IDeviceSenderService +import com.android.systemui.shared.mediattt.IUndoTransferCallback import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture @@ -18,8 +19,7 @@ import org.mockito.MockitoAnnotations @SmallTest class MediaTttSenderServiceTest : SysuiTestCase() { - private lateinit var service: MediaTttSenderService - private lateinit var callback: IDeviceSenderCallback + private lateinit var service: IDeviceSenderService @Mock private lateinit var controller: MediaTttChipControllerSender @@ -31,14 +31,14 @@ class MediaTttSenderServiceTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - service = MediaTttSenderService(context, controller) - callback = IDeviceSenderCallback.Stub.asInterface(service.onBind(null)) + val mediaTttSenderService = MediaTttSenderService(context, controller) + service = IDeviceSenderService.Stub.asInterface(mediaTttSenderService.onBind(null)) } @Test fun closeToReceiverToStartCast_controllerTriggeredWithCorrectState() { val name = "Fake name" - callback.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name)) + service.closeToReceiverToStartCast(mediaInfo, DeviceInfo(name)) val chipStateCaptor = argumentCaptor() verify(controller).displayChip(capture(chipStateCaptor)) @@ -50,7 +50,7 @@ class MediaTttSenderServiceTest : SysuiTestCase() { @Test fun closeToReceiverToEndCast_controllerTriggeredWithCorrectState() { val name = "Fake name" - callback.closeToReceiverToEndCast(mediaInfo, DeviceInfo(name)) + service.closeToReceiverToEndCast(mediaInfo, DeviceInfo(name)) val chipStateCaptor = argumentCaptor() verify(controller).displayChip(capture(chipStateCaptor)) @@ -61,7 +61,7 @@ class MediaTttSenderServiceTest : SysuiTestCase() { @Test fun transferToThisDeviceTriggered_controllerTriggeredWithCorrectState() { - callback.transferToThisDeviceTriggered(mediaInfo, DeviceInfo("Fake name")) + service.transferToThisDeviceTriggered(mediaInfo, DeviceInfo("Fake name")) verify(controller).displayChip(any()) } @@ -69,7 +69,7 @@ class MediaTttSenderServiceTest : SysuiTestCase() { @Test fun transferToReceiverTriggered_controllerTriggeredWithCorrectState() { val name = "Fake name" - callback.transferToReceiverTriggered(mediaInfo, DeviceInfo(name)) + service.transferToReceiverTriggered(mediaInfo, DeviceInfo(name)) val chipStateCaptor = argumentCaptor() verify(controller).displayChip(capture(chipStateCaptor)) @@ -81,18 +81,22 @@ class MediaTttSenderServiceTest : SysuiTestCase() { @Test fun transferToReceiverSucceeded_controllerTriggeredWithCorrectState() { val name = "Fake name" - callback.transferToReceiverSucceeded(mediaInfo, DeviceInfo(name)) + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + service.transferToReceiverSucceeded(mediaInfo, DeviceInfo(name), undoCallback) val chipStateCaptor = argumentCaptor() verify(controller).displayChip(capture(chipStateCaptor)) val chipState = chipStateCaptor.value!! assertThat(chipState.getChipTextString(context)).contains(name) + assertThat(chipState.undoCallback).isEqualTo(undoCallback) } @Test fun transferFailed_controllerTriggeredWithTransferFailedState() { - callback.transferFailed(mediaInfo, DeviceInfo("Fake name")) + service.transferFailed(mediaInfo, DeviceInfo("Fake name")) verify(controller).displayChip(any()) } From ca400ac4a9d4cc133f1f78a7aa743a7fd0ffda41 Mon Sep 17 00:00:00 2001 From: Caitlin Cassidy Date: Thu, 20 Jan 2022 23:43:33 +0000 Subject: [PATCH 5/6] [Media TTT] Add TransferToThisDeviceSucceeded callback. Bug: 203800643 Bug: 203800347 Test: verify `adb shell cmd statusbar media-ttt-chip-add-sender Device TransferToThisDeviceSucceeded` shows the chip Test: media.taptotransfer tests Change-Id: Iba98b91617e25126e03c99d03e0a628994e6516a --- .../shared/mediattt/IDeviceSenderService.aidl | 16 ++++ .../MediaTttCommandLineHelper.kt | 24 +++++- .../taptotransfer/sender/ChipStateSender.kt | 81 +++++++++++++++++-- .../sender/MediaTttChipControllerSender.kt | 24 +----- .../sender/MediaTttSenderService.kt | 22 +++++ .../MediaTttCommandLineHelperTest.kt | 19 +++++ .../MediaTttChipControllerSenderTest.kt | 67 +++++++++++++++ .../sender/MediaTttSenderServiceTest.kt | 14 ++++ 8 files changed, 236 insertions(+), 31 deletions(-) diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl index 3b9950930cbec..67259f4bb9399 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl @@ -101,6 +101,22 @@ interface IDeviceSenderService { in DeviceInfo otherDeviceInfo, in IUndoTransferCallback undoCallback); + /** + * Invoke to notify System UI that a media transfer from the receiver and back to this device + * (the sender) has finished successfully. + * + * Important notes: + * - This callback is for *ending* a cast. It should be used when media was previously being + * played on the receiver device and has been successfully transferred to play locally on + * this device instead. + * + * @param undoCallback will be invoked if the user chooses to undo this transfer. + */ + oneway void transferToThisDeviceSucceeded( + in MediaRoute2Info mediaInfo, + in DeviceInfo otherDeviceInfo, + in IUndoTransferCallback undoCallback); + /** * Invoke to notify System UI that the attempted transfer has failed. * diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index a2ab281b7ab1b..da93e925ac842 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -36,6 +36,7 @@ import com.android.systemui.media.taptotransfer.sender.MoveCloserToEndCast import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast import com.android.systemui.media.taptotransfer.sender.TransferFailed import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTriggered +import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceSucceeded import com.android.systemui.media.taptotransfer.sender.TransferToThisDeviceTriggered import com.android.systemui.media.taptotransfer.sender.TransferToReceiverSucceeded import com.android.systemui.shared.mediattt.DeviceInfo @@ -108,7 +109,7 @@ class MediaTttCommandLineHelper @Inject constructor( TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME -> { val undoCallback = object : IUndoTransferCallback.Stub() { override fun onUndoTriggered() { - Log.i(TAG, "Undo callback triggered") + Log.i(TAG, "Undo transfer to receiver callback triggered") // The external services that implement this callback would kick off a // transfer back to this device, so mimic that here. runOnService { senderService -> @@ -122,6 +123,23 @@ class MediaTttCommandLineHelper @Inject constructor( .transferToReceiverSucceeded(mediaInfo, otherDeviceInfo, undoCallback) } } + TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME -> { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() { + Log.i(TAG, "Undo transfer to this device callback triggered") + // The external services that implement this callback would kick off a + // transfer back to the receiver, so mimic that here. + runOnService { senderService -> + senderService + .transferToReceiverTriggered(mediaInfo, otherDeviceInfo) + } + } + } + runOnService { senderService -> + senderService + .transferToThisDeviceSucceeded(mediaInfo, otherDeviceInfo, undoCallback) + } + } TRANSFER_FAILED_COMMAND_NAME -> { runOnService { senderService -> senderService.transferFailed(mediaInfo, otherDeviceInfo) @@ -134,6 +152,7 @@ class MediaTttCommandLineHelper @Inject constructor( "$TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME, " + "$TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME, " + "$TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME, " + + "$TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME, " + TRANSFER_FAILED_COMMAND_NAME ) } @@ -245,6 +264,9 @@ val TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME = @VisibleForTesting val TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME = TransferToReceiverSucceeded::class.simpleName!! @VisibleForTesting +val TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME = + TransferToThisDeviceSucceeded::class.simpleName!! +@VisibleForTesting val TRANSFER_FAILED_COMMAND_NAME = TransferFailed::class.simpleName!! private const val APP_ICON_CONTENT_DESCRIPTION = "Fake media app icon" diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index 47969f2efcf12..c656df2e0a356 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt @@ -18,6 +18,7 @@ package com.android.systemui.media.taptotransfer.sender import android.content.Context import android.graphics.drawable.Drawable +import android.view.View import com.android.systemui.R import com.android.systemui.media.taptotransfer.common.MediaTttChipState import com.android.systemui.shared.mediattt.IUndoTransferCallback @@ -37,7 +38,18 @@ sealed class ChipStateSender( abstract fun getChipTextString(context: Context): String /** Returns true if the loading icon should be displayed and false otherwise. */ - abstract fun showLoading(): Boolean + open fun showLoading(): Boolean = false + + /** + * Returns a click listener for the undo button on the chip. Returns null if this chip state + * doesn't have an undo button. + * + * @param controllerSender passed as a parameter in case we want to display a new chip state + * when undo is clicked. + */ + open fun undoClickListener( + controllerSender: MediaTttChipControllerSender + ): View.OnClickListener? = null } /** @@ -55,8 +67,6 @@ class MoveCloserToStartCast( override fun getChipTextString(context: Context): String { return context.getString(R.string.media_move_closer_to_start_cast, otherDeviceName) } - - override fun showLoading() = false } /** @@ -74,8 +84,6 @@ class MoveCloserToEndCast( override fun getChipTextString(context: Context): String { return context.getString(R.string.media_move_closer_to_end_cast, otherDeviceName) } - - override fun showLoading() = false } /** @@ -128,7 +136,66 @@ class TransferToReceiverSucceeded( return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName) } - override fun showLoading() = false + override fun undoClickListener( + controllerSender: MediaTttChipControllerSender + ): View.OnClickListener? { + if (undoCallback == null) { + return null + } + + return View.OnClickListener { + this.undoCallback.onUndoTriggered() + // The external service should eventually send us a TransferToThisDeviceTriggered state, + // but that may take too long to go through the binder and the user may be confused as + // to why the UI hasn't changed yet. So, we immediately change the UI here. + controllerSender.displayChip( + TransferToThisDeviceTriggered( + this.appIconDrawable, + this.appIconContentDescription + ) + ) + } + } +} + +/** + * A state representing that a transfer back to this device has been successfully completed. + * + * @property otherDeviceName the name of the other device involved in the transfer. + * @property undoCallback if present, the callback that should be called when the user clicks the + * undo button. The undo button will only be shown if this is non-null. + */ +class TransferToThisDeviceSucceeded( + appIconDrawable: Drawable, + appIconContentDescription: String, + private val otherDeviceName: String, + val undoCallback: IUndoTransferCallback? = null +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_transfer_playing_this_device) + } + + override fun undoClickListener( + controllerSender: MediaTttChipControllerSender + ): View.OnClickListener? { + if (undoCallback == null) { + return null + } + + return View.OnClickListener { + this.undoCallback.onUndoTriggered() + // The external service should eventually send us a TransferToReceiverTriggered state, + // but that may take too long to go through the binder and the user may be confused as + // to why the UI hasn't changed yet. So, we immediately change the UI here. + controllerSender.displayChip( + TransferToReceiverTriggered( + this.appIconDrawable, + this.appIconContentDescription, + this.otherDeviceName + ) + ) + } + } } /** A state representing that a transfer has failed. */ @@ -139,6 +206,4 @@ class TransferFailed( override fun getChipTextString(context: Context): String { return context.getString(R.string.media_transfer_failed) } - - override fun showLoading() = false } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 30a2809f3c42d..453e3d627bc8b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -53,30 +53,10 @@ class MediaTttChipControllerSender @Inject constructor( if (chipState.showLoading()) { View.VISIBLE } else { View.GONE } // Undo - val undoClickListener: View.OnClickListener? = - if (chipState is TransferToReceiverSucceeded && chipState.undoCallback != null) - View.OnClickListener { - chipState.undoCallback.onUndoTriggered() - // The external service should eventually send us a - // TransferToThisDeviceTriggered state, but that may take too long to go through - // the binder and the user may be confused as to why the UI hasn't changed yet. - // So, we immediately change the UI here. - displayChip( - TransferToThisDeviceTriggered( - chipState.appIconDrawable, - chipState.appIconContentDescription - ) - ) - } - else - null val undoView = currentChipView.requireViewById(R.id.undo) - undoView.visibility = if (undoClickListener != null) { - View.VISIBLE - } else { - View.GONE - } + val undoClickListener = chipState.undoClickListener(this) undoView.setOnClickListener(undoClickListener) + undoView.visibility = if (undoClickListener != null) { View.VISIBLE } else { View.GONE } // Failure val showFailure = chipState is TransferFailed diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt index 8431e2598eef1..8d9d7a9f9ce84 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt @@ -78,6 +78,16 @@ class MediaTttSenderService @Inject constructor( mediaInfo, otherDeviceInfo, undoCallback ) } + + override fun transferToThisDeviceSucceeded( + mediaInfo: MediaRoute2Info, + otherDeviceInfo: DeviceInfo, + undoCallback: IUndoTransferCallback + ) { + this@MediaTttSenderService.transferToThisDeviceSucceeded( + mediaInfo, otherDeviceInfo, undoCallback + ) + } } // TODO(b/203800643): Use the app icon from the media info instead of a fake one. @@ -146,4 +156,16 @@ class MediaTttSenderService @Inject constructor( ) controller.displayChip(chipState) } + + private fun transferToThisDeviceSucceeded( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback + ) { + val chipState = TransferToThisDeviceSucceeded( + appIconDrawable = fakeAppIconDrawable, + appIconContentDescription = mediaInfo.name.toString(), + otherDeviceName = otherDeviceInfo.name, + undoCallback = undoCallback + ) + controller.displayChip(chipState) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index f2e224705e59c..9b163058ab32f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -166,6 +166,18 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) } + @Test + fun sender_transferToThisDeviceSucceeded_chipDisplayWithCorrectState() { + commandRegistry.onShellCommand(pw, getTransferToThisDeviceSucceededCommand()) + + assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() + + val deviceInfoCaptor = argumentCaptor() + verify(mediaSenderService) + .transferToThisDeviceSucceeded(any(), capture(deviceInfoCaptor), any()) + assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) + } + @Test fun sender_transferFailed_serviceCallbackCalled() { commandRegistry.onShellCommand(pw, getTransferFailedCommand()) @@ -230,6 +242,13 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME ) + private fun getTransferToThisDeviceSucceededCommand(): Array = + arrayOf( + ADD_CHIP_COMMAND_SENDER_TAG, + DEVICE_NAME, + TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME + ) + private fun getTransferFailedCommand(): Array = arrayOf( ADD_CHIP_COMMAND_SENDER_TAG, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index 319b692d004cd..509ae337abb8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -169,6 +169,67 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { .isEqualTo(transferToThisDeviceTriggered().getChipTextString(context)) } + @Test + fun transferToThisDeviceSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() { + val state = transferToThisDeviceSucceeded() + controllerSender.displayChip(state) + + val chipView = getChipView() + assertThat(chipView.getAppIconView().drawable).isEqualTo(appIconDrawable) + assertThat(chipView.getAppIconView().contentDescription).isEqualTo(APP_ICON_CONTENT_DESC) + assertThat(chipView.getChipText()).isEqualTo(state.getChipTextString(context)) + assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE) + assertThat(chipView.getFailureIcon().visibility).isEqualTo(View.GONE) + } + + @Test + fun transferToThisDeviceSucceeded_nullUndoRunnable_noUndo() { + controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback = null)) + + val chipView = getChipView() + assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) + } + + @Test + fun transferToThisDeviceSucceeded_withUndoRunnable_undoWithClick() { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback)) + + val chipView = getChipView() + assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE) + assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue() + } + + @Test + fun transferToThisDeviceSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() { + var undoCallbackCalled = false + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() { + undoCallbackCalled = true + } + } + + controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback)) + getChipView().getUndoButton().performClick() + + assertThat(undoCallbackCalled).isTrue() + } + + @Test + fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToReceiverTriggered() { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + controllerSender.displayChip(transferToThisDeviceSucceeded(undoCallback)) + + getChipView().getUndoButton().performClick() + + assertThat(getChipView().getChipText()) + .isEqualTo(transferToReceiverTriggered().getChipTextString(context)) + } + @Test fun transferFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() { val state = transferFailed() @@ -269,6 +330,12 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback ) + /** Helper method providing default parameters to not clutter up the tests. */ + private fun transferToThisDeviceSucceeded(undoCallback: IUndoTransferCallback? = null) = + TransferToThisDeviceSucceeded( + appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME, undoCallback + ) + /** Helper method providing default parameters to not clutter up the tests. */ private fun transferFailed() = TransferFailed(appIconDrawable, APP_ICON_CONTENT_DESC) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt index 177a1c275fba7..e7304d49f2469 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt @@ -94,6 +94,20 @@ class MediaTttSenderServiceTest : SysuiTestCase() { assertThat(chipState.undoCallback).isEqualTo(undoCallback) } + @Test + fun transferToThisDeviceSucceeded_controllerTriggeredWithCorrectState() { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + service.transferToThisDeviceSucceeded(mediaInfo, DeviceInfo("name"), undoCallback) + + val chipStateCaptor = argumentCaptor() + verify(controller).displayChip(capture(chipStateCaptor)) + + val chipState = chipStateCaptor.value!! + assertThat(chipState.undoCallback).isEqualTo(undoCallback) + } + @Test fun transferFailed_controllerTriggeredWithTransferFailedState() { service.transferFailed(mediaInfo, DeviceInfo("Fake name")) From 180e9686d6ee621fa8bb927226b335e04e4ef12b Mon Sep 17 00:00:00 2001 From: Caitlin Cassidy Date: Fri, 21 Jan 2022 00:17:44 +0000 Subject: [PATCH 6/6] [Media TTT] Add NoLongerCloseToReceiver callback. Bug: 203800643 Bug: 203800347 Test: verify `adb shell cmd statusbar media-ttt-chip-sender Device NoLongerCloseToReceiver` hides the chip Test: media.taptotransfer tests Change-Id: Iceea7c10b23dc1b175e11335ca4fcf695ca98fb6 --- .../shared/mediattt/IDeviceSenderService.aidl | 6 +++ .../systemui/media/dagger/MediaModule.java | 2 - .../MediaTttCommandLineHelper.kt | 42 ++++++---------- .../common/MediaTttChipControllerCommon.kt | 3 ++ .../sender/MediaTttSenderService.kt | 11 ++++ .../MediaTttCommandLineHelperTest.kt | 50 ++++++++----------- .../sender/MediaTttSenderServiceTest.kt | 7 +++ 7 files changed, 65 insertions(+), 56 deletions(-) diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl index 67259f4bb9399..eb1c9d058e208 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/mediattt/IDeviceSenderService.aidl @@ -124,4 +124,10 @@ interface IDeviceSenderService { * on the receiver and the transfer that should've *ended* the playing on the receiver. */ oneway void transferFailed(in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); + + /** + * Invoke to notify System UI that this device is no longer close to the receiver device. + */ + oneway void noLongerCloseToReceiver( + in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index dd60b30acad9e..4baef3aef3099 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -112,7 +112,6 @@ public interface MediaModule { MediaTttFlags mediaTttFlags, CommandRegistry commandRegistry, Context context, - MediaTttChipControllerSender mediaTttChipControllerSender, MediaTttChipControllerReceiver mediaTttChipControllerReceiver) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); @@ -121,7 +120,6 @@ public interface MediaModule { new MediaTttCommandLineHelper( commandRegistry, context, - mediaTttChipControllerSender, mediaTttChipControllerReceiver)); } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index da93e925ac842..37208515120a9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -30,7 +30,6 @@ import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver -import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService import com.android.systemui.media.taptotransfer.sender.MoveCloserToEndCast import com.android.systemui.media.taptotransfer.sender.MoveCloserToStartCast @@ -55,7 +54,6 @@ import javax.inject.Inject class MediaTttCommandLineHelper @Inject constructor( commandRegistry: CommandRegistry, private val context: Context, - private val mediaTttChipControllerSender: MediaTttChipControllerSender, private val mediaTttChipControllerReceiver: MediaTttChipControllerReceiver, ) { private var senderService: IDeviceSenderService? = null @@ -67,17 +65,15 @@ class MediaTttCommandLineHelper @Inject constructor( } init { - commandRegistry.registerCommand( - ADD_CHIP_COMMAND_SENDER_TAG) { AddChipCommandSender() } - commandRegistry.registerCommand( - REMOVE_CHIP_COMMAND_SENDER_TAG) { RemoveChipCommandSender() } + commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() } commandRegistry.registerCommand( ADD_CHIP_COMMAND_RECEIVER_TAG) { AddChipCommandReceiver() } commandRegistry.registerCommand( REMOVE_CHIP_COMMAND_RECEIVER_TAG) { RemoveChipCommandReceiver() } } - inner class AddChipCommandSender : Command { + /** All commands for the sender device. */ + inner class SenderCommand : Command { override fun execute(pw: PrintWriter, args: List) { val otherDeviceName = args[0] val mediaInfo = MediaRoute2Info.Builder("id", "Test Name") @@ -145,24 +141,29 @@ class MediaTttCommandLineHelper @Inject constructor( senderService.transferFailed(mediaInfo, otherDeviceInfo) } } + NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME -> { + runOnService { senderService -> + senderService.noLongerCloseToReceiver(mediaInfo, otherDeviceInfo) + context.unbindService(senderServiceConnection) + } + } else -> { - pw.println("Chip type must be one of " + + pw.println("Sender command must be one of " + "$MOVE_CLOSER_TO_START_CAST_COMMAND_NAME, " + "$MOVE_CLOSER_TO_END_CAST_COMMAND_NAME, " + "$TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME, " + "$TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME, " + "$TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME, " + "$TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME, " + - TRANSFER_FAILED_COMMAND_NAME + "$TRANSFER_FAILED_COMMAND_NAME, " + + NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME ) } } } override fun help(pw: PrintWriter) { - pw.println("Usage: adb shell cmd statusbar " + - "$ADD_CHIP_COMMAND_SENDER_TAG " - ) + pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND ") } private fun runOnService(command: SenderServiceCommand) { @@ -185,19 +186,6 @@ class MediaTttCommandLineHelper @Inject constructor( } } - /** A command to REMOVE the media ttt chip on the SENDER device. */ - inner class RemoveChipCommandSender : Command { - override fun execute(pw: PrintWriter, args: List) { - mediaTttChipControllerSender.removeChip() - if (senderService != null) { - context.unbindService(senderServiceConnection) - } - } - override fun help(pw: PrintWriter) { - pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_SENDER_TAG") - } - } - /** A command to DISPLAY the media ttt chip on the RECEIVER device. */ inner class AddChipCommandReceiver : Command { override fun execute(pw: PrintWriter, args: List) { @@ -245,7 +233,7 @@ class MediaTttCommandLineHelper @Inject constructor( } @VisibleForTesting -const val ADD_CHIP_COMMAND_SENDER_TAG = "media-ttt-chip-add-sender" +const val SENDER_COMMAND = "media-ttt-chip-sender" @VisibleForTesting const val REMOVE_CHIP_COMMAND_SENDER_TAG = "media-ttt-chip-remove-sender" @VisibleForTesting @@ -268,6 +256,8 @@ val TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME = TransferToThisDeviceSucceeded::class.simpleName!! @VisibleForTesting val TRANSFER_FAILED_COMMAND_NAME = TransferFailed::class.simpleName!! +@VisibleForTesting +val NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME = "NoLongerCloseToReceiver" private const val APP_ICON_CONTENT_DESCRIPTION = "Fake media app icon" private const val TAG = "MediaTapToTransferCli" diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt index 67721a543427c..adae07b58e7c7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt @@ -81,6 +81,9 @@ abstract class MediaTttChipControllerCommon( /** Hides the chip. */ fun removeChip() { + // TODO(b/203800347): We may not want to hide the chip if we're currently in a + // TransferTriggered state: Once the user has initiated the transfer, they should be able + // to move away from the receiver device but still see the status of the transfer. if (chipView == null) { return } windowManager.removeView(chipView) chipView = null diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt index 8d9d7a9f9ce84..717752e536b0e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderService.kt @@ -88,6 +88,13 @@ class MediaTttSenderService @Inject constructor( mediaInfo, otherDeviceInfo, undoCallback ) } + + override fun noLongerCloseToReceiver( + mediaInfo: MediaRoute2Info, + otherDeviceInfo: DeviceInfo + ) { + this@MediaTttSenderService.noLongerCloseToReceiver() + } } // TODO(b/203800643): Use the app icon from the media info instead of a fake one. @@ -168,4 +175,8 @@ class MediaTttSenderService @Inject constructor( ) controller.displayChip(chipState) } + + private fun noLongerCloseToReceiver() { + controller.removeChip() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index 9b163058ab32f..a1ec38f630ed3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -21,7 +21,6 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver -import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender import com.android.systemui.media.taptotransfer.sender.MediaTttSenderService import com.android.systemui.shared.mediattt.DeviceInfo import com.android.systemui.shared.mediattt.IDeviceSenderService @@ -51,8 +50,6 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { private lateinit var mediaTttCommandLineHelper: MediaTttCommandLineHelper - @Mock - private lateinit var mediaTttChipControllerSender: MediaTttChipControllerSender @Mock private lateinit var mediaTttChipControllerReceiver: MediaTttChipControllerReceiver @Mock @@ -72,27 +69,15 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { MediaTttCommandLineHelper( commandRegistry, context, - mediaTttChipControllerSender, mediaTttChipControllerReceiver, ) } @Test(expected = IllegalStateException::class) - fun constructor_addSenderCommandAlreadyRegistered() { - // Since creating the chip controller should automatically register the add command, it + fun constructor_senderCommandAlreadyRegistered() { + // Since creating the chip controller should automatically register the sender command, it // should throw when registering it again. - commandRegistry.registerCommand( - ADD_CHIP_COMMAND_SENDER_TAG - ) { EmptyCommand() } - } - - @Test(expected = IllegalStateException::class) - fun constructor_removeSenderCommandAlreadyRegistered() { - // Since creating the chip controller should automatically register the remove command, it - // should throw when registering it again. - commandRegistry.registerCommand( - REMOVE_CHIP_COMMAND_SENDER_TAG - ) { EmptyCommand() } + commandRegistry.registerCommand(SENDER_COMMAND) { EmptyCommand() } } @Test(expected = IllegalStateException::class) @@ -187,10 +172,12 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { } @Test - fun sender_removeCommand_chipRemoved() { - commandRegistry.onShellCommand(pw, arrayOf(REMOVE_CHIP_COMMAND_SENDER_TAG)) + fun sender_noLongerCloseToReceiver_serviceCallbackCalledAndServiceUnbound() { + commandRegistry.onShellCommand(pw, getNoLongerCloseToReceiverCommand()) - verify(mediaTttChipControllerSender).removeChip() + // Once we're no longer close to the receiver, we should unbind the service. + assertThat(context.isBound(mediaSenderServiceComponentName)).isFalse() + verify(mediaSenderService).noLongerCloseToReceiver(any(), any()) } @Test @@ -209,53 +196,60 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { private fun getMoveCloserToStartCastCommand(): Array = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, MOVE_CLOSER_TO_START_CAST_COMMAND_NAME ) private fun getMoveCloserToEndCastCommand(): Array = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, MOVE_CLOSER_TO_END_CAST_COMMAND_NAME ) private fun getTransferToReceiverTriggeredCommand(): Array = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME ) private fun getTransferToThisDeviceTriggeredCommand(): Array = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME ) private fun getTransferToReceiverSucceededCommand(): Array = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME ) private fun getTransferToThisDeviceSucceededCommand(): Array = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME ) private fun getTransferFailedCommand(): Array = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, TRANSFER_FAILED_COMMAND_NAME ) + private fun getNoLongerCloseToReceiverCommand(): Array = + arrayOf( + SENDER_COMMAND, + DEVICE_NAME, + NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME + ) + class EmptyCommand : Command { override fun execute(pw: PrintWriter, args: List) { } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt index e7304d49f2469..11b727ec507ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderServiceTest.kt @@ -114,4 +114,11 @@ class MediaTttSenderServiceTest : SysuiTestCase() { verify(controller).displayChip(any()) } + + @Test + fun noLongerCloseToReceiver_controllerRemoveChipTriggered() { + service.noLongerCloseToReceiver(mediaInfo, DeviceInfo("Fake name")) + + verify(controller).removeChip() + } }