diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index aebddc9d98f36..a7cd33d92a9ab 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2189,6 +2189,8 @@
Undo
Move closer to play on %1$s
+
+ Move closer to %1$s to play here
Playing on %1$s
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 484791df053eb..a68397deba2aa 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
@@ -43,4 +43,20 @@ interface IDeviceSenderCallback {
*/
oneway void closeToReceiverToStartCast(
in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
+
+ /**
+ * Invoke to notify System UI that this device (the sender) is close to a receiver device, so
+ * the user can potentially *end* a cast on the receiver device if the user moves this device a
+ * bit closer.
+ *
+ * Important notes:
+ * - When this callback triggers, the device is close enough to inform the user that
+ * transferring is an option, but the device is *not* close enough to actually initiate a
+ * transfer yet.
+ * - This callback is for *ending* a cast. It should be used when media is currently being
+ * played on the receiver device and the media should be transferred to play locally
+ * instead.
+ */
+ oneway void closeToReceiverToEndCast(
+ in MediaRoute2Info mediaInfo, in DeviceInfo otherDeviceInfo);
}
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 460d38f45b4d9..6142188c7d410 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -33,6 +33,7 @@ import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerR
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
import com.android.systemui.media.taptotransfer.sender.TransferInitiated
import com.android.systemui.media.taptotransfer.sender.TransferSucceeded
@@ -90,6 +91,11 @@ class MediaTttCommandLineHelper @Inject constructor(
senderCallback.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
}
}
+ MOVE_CLOSER_TO_END_CAST_COMMAND_NAME -> {
+ runOnService { senderCallback ->
+ senderCallback.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo)
+ }
+ }
// TODO(b/203800643): Migrate other commands to invoke the service instead of the
// controller.
@@ -119,6 +125,7 @@ class MediaTttCommandLineHelper @Inject constructor(
else -> {
pw.println("Chip type must be one of " +
"$MOVE_CLOSER_TO_START_CAST_COMMAND_NAME, " +
+ "$MOVE_CLOSER_TO_END_CAST_COMMAND_NAME, " +
"$TRANSFER_INITIATED_COMMAND_NAME, " +
TRANSFER_SUCCEEDED_COMMAND_NAME
)
@@ -226,6 +233,8 @@ const val REMOVE_CHIP_COMMAND_RECEIVER_TAG = "media-ttt-chip-remove-receiver"
@VisibleForTesting
val MOVE_CLOSER_TO_START_CAST_COMMAND_NAME = MoveCloserToStartCast::class.simpleName!!
@VisibleForTesting
+val MOVE_CLOSER_TO_END_CAST_COMMAND_NAME = MoveCloserToEndCast::class.simpleName!!
+@VisibleForTesting
val TRANSFER_INITIATED_COMMAND_NAME = TransferInitiated::class.simpleName!!
@VisibleForTesting
val TRANSFER_SUCCEEDED_COMMAND_NAME = TransferSucceeded::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 dd434e7756fbe..55dffa7513064 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
@@ -55,6 +55,22 @@ class MoveCloserToStartCast(
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).
+ */
+class MoveCloserToEndCast(
+ appIconDrawable: Drawable,
+ appIconContentDescription: String,
+ otherDeviceName: String,
+) : ChipStateSender(
+ appIconDrawable,
+ appIconContentDescription,
+ R.string.media_move_closer_to_end_cast,
+ otherDeviceName
+)
+
/**
* A state representing that a transfer has been initiated (but not completed).
*
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 b56a69903ea4f..9142056842a01 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
@@ -43,6 +43,12 @@ class MediaTttSenderService @Inject constructor(
) {
this@MediaTttSenderService.closeToReceiverToStartCast(mediaInfo, otherDeviceInfo)
}
+
+ override fun closeToReceiverToEndCast(
+ mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo
+ ) {
+ this@MediaTttSenderService.closeToReceiverToEndCast(mediaInfo, otherDeviceInfo)
+ }
}
// TODO(b/203800643): Use the app icon from the media info instead of a fake one.
@@ -63,4 +69,13 @@ class MediaTttSenderService @Inject constructor(
)
controller.displayChip(chipState)
}
+
+ private fun closeToReceiverToEndCast(mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo) {
+ val chipState = MoveCloserToEndCast(
+ appIconDrawable = fakeAppIconDrawable,
+ appIconContentDescription = mediaInfo.name.toString(),
+ otherDeviceName = otherDeviceInfo.name
+ )
+ 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 4839bdea1b701..e6673a5fcbea8 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
@@ -126,6 +126,17 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() {
assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
}
+ @Test
+ fun sender_moveCloserToEndCast_serviceCallbackCalled() {
+ commandRegistry.onShellCommand(pw, getMoveCloserToEndCastCommand())
+
+ assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue()
+
+ val deviceInfoCaptor = argumentCaptor()
+ verify(mediaSenderService).closeToReceiverToEndCast(any(), capture(deviceInfoCaptor))
+ assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME)
+ }
+
@Test
fun sender_transferInitiated_chipDisplayWithCorrectState() {
commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
@@ -168,6 +179,13 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() {
MOVE_CLOSER_TO_START_CAST_COMMAND_NAME
)
+ private fun getMoveCloserToEndCastCommand(): Array =
+ arrayOf(
+ ADD_CHIP_COMMAND_SENDER_TAG,
+ DEVICE_NAME,
+ MOVE_CLOSER_TO_END_CAST_COMMAND_NAME
+ )
+
private fun getTransferInitiatedCommand(): 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 ecc4c46634b98..b58eecbfc85af 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
@@ -77,6 +77,18 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
}
+ @Test
+ fun moveCloserToEndCast_appIcon_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
+ controllerSender.displayChip(moveCloserToEndCast())
+
+ 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.getLoadingIconVisibility()).isEqualTo(View.GONE)
+ assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+ }
+
@Test
fun transferInitiated_futureNotResolvedYet_appIcon_loadingIcon_noUndo() {
val future: SettableFuture = SettableFuture.create()
@@ -243,6 +255,10 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
private fun moveCloserToStartCast() =
MoveCloserToStartCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+ /** Helper method providing default parameters to not clutter up the tests. */
+ private fun moveCloserToEndCast() =
+ MoveCloserToEndCast(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
+
/** Helper method providing default parameters to not clutter up the tests. */
private fun transferInitiated(
future: Future = TEST_FUTURE
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 8f64698a5a6c1..e9ee0bdbd4c91 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
@@ -45,4 +45,16 @@ class MediaTttSenderServiceTest : SysuiTestCase() {
val chipState = chipStateCaptor.value!!
assertThat(chipState.otherDeviceName).isEqualTo(name)
}
+
+ @Test
+ fun closeToReceiverToEndCast_controllerTriggeredWithMoveCloserToEndCastState() {
+ val name = "Fake name"
+ callback.closeToReceiverToEndCast(mediaInfo, DeviceInfo(name))
+
+ val chipStateCaptor = argumentCaptor()
+ verify(controller).displayChip(capture(chipStateCaptor))
+
+ val chipState = chipStateCaptor.value!!
+ assertThat(chipState.otherDeviceName).isEqualTo(name)
+ }
}