diff options
| author | 2022-01-24 20:04:34 +0000 | |
|---|---|---|
| committer | 2022-01-24 20:04:34 +0000 | |
| commit | dbd0419baba5664bf6eef5c8bf2a9d6779afa657 (patch) | |
| tree | 9de413f0a3d344daa8f912d96856f40bb9f0e972 | |
| parent | 8919e54dbe7c10c5f514f0cdf30359cef8def90d (diff) | |
| parent | 180e9686d6ee621fa8bb927226b335e04e4ef12b (diff) | |
Merge changes Iceea7c10,Iba98b916,I912caa44,Id5aef5c4,Iddc05936, ...
* changes:
[Media TTT] Add NoLongerCloseToReceiver callback.
[Media TTT] Add TransferToThisDeviceSucceeded callback.
[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.
[Media TTT] Add the #transferToReceiverSucceeded callback.
[Media TTT] Add transferToThisDeviceTriggered callback.
[Media TTT] Update chip states to not require otherDeviceName if they don't need it. Define a #getChipTextString method instead.
12 files changed, 672 insertions, 215 deletions
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 34e5aef99e8c..41d5735c8d80 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2161,7 +2161,9 @@ <!-- Text to ask the user to move their device closer to a different device (deviceName) in order to transfer media from the different device and back onto the current device. [CHAR LIMIT=75] --> <string name="media_move_closer_to_end_cast">Move closer to <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g> to play here</string> <!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] --> - <string name="media_transfer_playing">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string> + <string name="media_transfer_playing_different_device">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string> + <!-- Text informing the user that their media is now playing on this device. [CHAR LIMIT=50] --> + <string name="media_transfer_playing_this_device">Playing on this phone</string> <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIMIT=50] --> <string name="media_transfer_failed">Something went wrong</string> 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 index 8db3e9d135a0..eb1c9d058e20 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 @@ -73,10 +74,60 @@ interface IDeviceSenderCallback { 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 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. + * + * @param undoCallback will be invoked if the user chooses to undo this transfer. + */ + oneway void transferToReceiverSucceeded( + in MediaRoute2Info mediaInfo, + 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. * * This callback will be used for both the transfer that should've *started* playing the media * 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/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 000000000000..b47be8736d23 --- /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/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index dd60b30acad9..4baef3aef309 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 e8a847f6ae41..37208515120a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -30,15 +30,17 @@ 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 import com.android.systemui.media.taptotransfer.sender.TransferFailed import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTriggered -import com.android.systemui.media.taptotransfer.sender.TransferSucceeded +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 -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 @@ -52,10 +54,9 @@ import javax.inject.Inject class MediaTttCommandLineHelper @Inject constructor( commandRegistry: CommandRegistry, private val context: Context, - 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 = @@ -64,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<String>) { val otherDeviceName = args[0] val mediaInfo = MediaRoute2Info.Builder("id", "Test Name") @@ -84,65 +83,99 @@ 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) } } - // 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_THIS_DEVICE_TRIGGERED_COMMAND_NAME -> { + runOnService { senderService -> + senderService.transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo) + } + } + TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME -> { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() { + 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 -> + senderService + .transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo) + } + } + } + runOnService { senderService -> + senderService + .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 { senderCallback -> - senderCallback.transferFailed(mediaInfo, otherDeviceInfo) + runOnService { senderService -> + 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_SUCCEEDED_COMMAND_NAME, " + - TRANSFER_FAILED_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, " + + NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME ) } } } override fun help(pw: PrintWriter) { - pw.println("Usage: adb shell cmd statusbar " + - "$ADD_CHIP_COMMAND_SENDER_TAG <deviceName> <chipStatus>" - ) + pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND <deviceName> <chipStatus>") } - 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), @@ -153,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<String>) { - mediaTttChipControllerSender.removeChip() - if (senderCallback != 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<String>) { @@ -188,36 +208,32 @@ 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) - } - - private val fakeUndoRunnable = Runnable { - Log.i(TAG, "Undo runnable triggered") + /** 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) } } @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 @@ -231,9 +247,17 @@ 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_SUCCEEDED_COMMAND_NAME = TransferSucceeded::class.simpleName!! +val TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME = + TransferToThisDeviceTriggered::class.simpleName!! +@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!! +@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 67721a543427..adae07b58e7c 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<T : MediaTttChipState>( /** 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/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt index 1fd3af4ce8d9..c656df2e0a35 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,10 +16,12 @@ package com.android.systemui.media.taptotransfer.sender +import android.content.Context import android.graphics.drawable.Drawable -import androidx.annotation.StringRes +import android.view.View 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 @@ -27,91 +29,181 @@ 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 + + /** Returns true if the loading icon should be displayed and false otherwise. */ + 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 +} /** * 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_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 +} + +/** + * 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 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 undoCallback: IUndoTransferCallback? = null +) : ChipStateSender(appIconDrawable, appIconContentDescription) { + override fun getChipTextString(context: Context): String { + return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName) + } + + 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 has been successfully completed. + * A state representing that a transfer back to this device has been successfully completed. * - * @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 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 TransferSucceeded( +class TransferToThisDeviceSucceeded( appIconDrawable: Drawable, appIconContentDescription: String, - otherDeviceName: String, - val undoRunnable: Runnable? = null -) : ChipStateSender(appIconDrawable, - appIconContentDescription, - R.string.media_transfer_playing, - otherDeviceName -) + 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. */ 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 84672ab9a31c..453e3d627bc8 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,27 +45,18 @@ class MediaTttChipControllerSender @Inject constructor( // Text currentChipView.requireViewById<TextView>(R.id.text).apply { - text = context.getString(chipState.chipText, chipState.otherDeviceName) + text = chipState.getChipTextString(context) } // Loading - val showLoading = chipState is TransferToReceiverTriggered currentChipView.requireViewById<View>(R.id.loading).visibility = - if (showLoading) { View.VISIBLE } else { View.GONE } + if (chipState.showLoading()) { View.VISIBLE } else { View.GONE } // Undo - val undoClickListener: View.OnClickListener? = - if (chipState is TransferSucceeded && chipState.undoRunnable != null) - View.OnClickListener { chipState.undoRunnable.run() } - else - null val undoView = currentChipView.requireViewById<View>(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 0fe324eb557d..717752e536b0 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 ) { @@ -53,7 +54,7 @@ class MediaTttSenderService @Inject constructor( override fun transferFailed( mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo ) { - this@MediaTttSenderService.transferFailed(mediaInfo, otherDeviceInfo) + this@MediaTttSenderService.transferFailed(mediaInfo) } override fun transferToReceiverTriggered( @@ -61,6 +62,39 @@ class MediaTttSenderService @Inject constructor( ) { this@MediaTttSenderService.transferToReceiverTriggered(mediaInfo, otherDeviceInfo) } + + override fun transferToThisDeviceTriggered( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo + ) { + this@MediaTttSenderService.transferToThisDeviceTriggered(mediaInfo) + } + + override fun transferToReceiverSucceeded( + mediaInfo: MediaRoute2Info, + otherDeviceInfo: DeviceInfo, + undoCallback: IUndoTransferCallback + ) { + this@MediaTttSenderService.transferToReceiverSucceeded( + mediaInfo, otherDeviceInfo, undoCallback + ) + } + + override fun transferToThisDeviceSucceeded( + mediaInfo: MediaRoute2Info, + otherDeviceInfo: DeviceInfo, + undoCallback: IUndoTransferCallback + ) { + this@MediaTttSenderService.transferToThisDeviceSucceeded( + 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. @@ -91,11 +125,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) } @@ -110,4 +143,40 @@ class MediaTttSenderService @Inject constructor( ) controller.displayChip(chipState) } + + private fun transferToThisDeviceTriggered(mediaInfo: MediaRoute2Info) { + val chipState = TransferToThisDeviceTriggered( + appIconDrawable = fakeAppIconDrawable, + appIconContentDescription = mediaInfo.name.toString() + ) + controller.displayChip(chipState) + } + + private fun transferToReceiverSucceeded( + mediaInfo: MediaRoute2Info, otherDeviceInfo: DeviceInfo, undoCallback: IUndoTransferCallback + ) { + val chipState = TransferToReceiverSucceeded( + appIconDrawable = fakeAppIconDrawable, + appIconContentDescription = mediaInfo.name.toString(), + otherDeviceName = otherDeviceInfo.name, + undoCallback = undoCallback + ) + 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) + } + + 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 cf5d47747e4c..a1ec38f630ed 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,9 @@ 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.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 @@ -51,11 +51,9 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { private lateinit var mediaTttCommandLineHelper: MediaTttCommandLineHelper @Mock - private lateinit var mediaTttChipControllerSender: MediaTttChipControllerSender - @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 @@ -71,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 - // 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 + fun constructor_senderCommandAlreadyRegistered() { + // Since creating the chip controller should automatically register the sender 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) @@ -146,10 +132,35 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { } @Test - fun sender_transferSucceeded_chipDisplayWithCorrectState() { - commandRegistry.onShellCommand(pw, getTransferSucceededCommand()) + fun sender_transferToThisDeviceTriggered_chipDisplayWithCorrectState() { + commandRegistry.onShellCommand(pw, getTransferToThisDeviceTriggeredCommand()) + + assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() + verify(mediaSenderService).transferToThisDeviceTriggered(any(), any()) + } + + @Test + fun sender_transferToReceiverSucceeded_chipDisplayWithCorrectState() { + commandRegistry.onShellCommand(pw, getTransferToReceiverSucceededCommand()) + + assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() + + val deviceInfoCaptor = argumentCaptor<DeviceInfo>() + verify(mediaSenderService) + .transferToReceiverSucceeded(any(), capture(deviceInfoCaptor), any()) + assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) + } + + @Test + fun sender_transferToThisDeviceSucceeded_chipDisplayWithCorrectState() { + commandRegistry.onShellCommand(pw, getTransferToThisDeviceSucceededCommand()) + + assertThat(context.isBound(mediaSenderServiceComponentName)).isTrue() - verify(mediaTttChipControllerSender).displayChip(any(TransferSucceeded::class.java)) + val deviceInfoCaptor = argumentCaptor<DeviceInfo>() + verify(mediaSenderService) + .transferToThisDeviceSucceeded(any(), capture(deviceInfoCaptor), any()) + assertThat(deviceInfoCaptor.value!!.name).isEqualTo(DEVICE_NAME) } @Test @@ -161,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 @@ -183,39 +196,60 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { private fun getMoveCloserToStartCastCommand(): Array<String> = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, MOVE_CLOSER_TO_START_CAST_COMMAND_NAME ) private fun getMoveCloserToEndCastCommand(): Array<String> = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, MOVE_CLOSER_TO_END_CAST_COMMAND_NAME ) private fun getTransferToReceiverTriggeredCommand(): Array<String> = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, TRANSFER_TO_RECEIVER_TRIGGERED_COMMAND_NAME ) - private fun getTransferSucceededCommand(): Array<String> = + private fun getTransferToThisDeviceTriggeredCommand(): Array<String> = + arrayOf( + SENDER_COMMAND, + DEVICE_NAME, + TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME + ) + + private fun getTransferToReceiverSucceededCommand(): Array<String> = + arrayOf( + SENDER_COMMAND, + DEVICE_NAME, + TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME + ) + + private fun getTransferToThisDeviceSucceededCommand(): Array<String> = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, - TRANSFER_SUCCEEDED_COMMAND_NAME + TRANSFER_TO_THIS_DEVICE_SUCCEEDED_COMMAND_NAME ) private fun getTransferFailedCommand(): Array<String> = arrayOf( - ADD_CHIP_COMMAND_SENDER_TAG, + SENDER_COMMAND, DEVICE_NAME, TRANSFER_FAILED_COMMAND_NAME ) + private fun getNoLongerCloseToReceiverCommand(): Array<String> = + arrayOf( + SENDER_COMMAND, + DEVICE_NAME, + NO_LONGER_CLOSE_TO_RECEIVER_COMMAND_NAME + ) + class EmptyCommand : Command { override fun execute(pw: PrintWriter, args: List<String>) { } 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 e9ddf3d857b1..509ae337abb8 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 @@ -53,12 +54,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 +68,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,40 +82,59 @@ 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()).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 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()).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) } @Test - fun transferSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() { - controllerSender.displayChip(transferSucceeded()) + fun transferToReceiverSucceeded_appIcon_deviceName_noLoadingIcon_noFailureIcon() { + val state = transferToReceiverSucceeded() + 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) } @Test - fun transferSucceededNullUndoRunnable_noUndo() { - controllerSender.displayChip(transferSucceeded(undoRunnable = null)) + fun transferToReceiverSucceeded_nullUndoRunnable_noUndo() { + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback = null)) val chipView = getChipView() assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE) } @Test - fun transferSucceededWithUndoRunnable_undoWithClick() { - controllerSender.displayChip(transferSucceeded { }) + fun transferToReceiverSucceeded_withUndoRunnable_undoWithClick() { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback)) val chipView = getChipView() assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE) @@ -120,24 +142,103 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { } @Test - fun transferSucceededWithUndoRunnable_undoButtonClickRunsRunnable() { - var runnableRun = false - val runnable = Runnable { runnableRun = true } + fun transferToReceiverSucceeded_withUndoRunnable_undoButtonClickRunsRunnable() { + var undoCallbackCalled = false + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() { + undoCallbackCalled = true + } + } + + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback)) + getChipView().getUndoButton().performClick() + + assertThat(undoCallbackCalled).isTrue() + } + + @Test + fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() { + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + controllerSender.displayChip(transferToReceiverSucceeded(undoCallback)) - controllerSender.displayChip(transferSucceeded(undoRunnable = runnable)) getChipView().getUndoButton().performClick() - assertThat(runnableRun).isTrue() + assertThat(getChipView().getChipText()) + .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() { - 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) @@ -154,7 +255,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun changeFromTransferTriggeredToTransferSucceeded_loadingIconDisappears() { controllerSender.displayChip(transferToReceiverTriggered()) - controllerSender.displayChip(transferSucceeded()) + controllerSender.displayChip(transferToReceiverSucceeded()) assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE) } @@ -162,14 +263,20 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Test fun changeFromTransferTriggeredToTransferSucceeded_undoButtonAppears() { controllerSender.displayChip(transferToReceiverTriggered()) - controllerSender.displayChip(transferSucceeded { }) + controllerSender.displayChip( + transferToReceiverSucceeded( + object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + ) + ) 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) @@ -214,13 +321,23 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { TransferToReceiverTriggered(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME) /** 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 transferToThisDeviceTriggered() = + TransferToThisDeviceTriggered(appIconDrawable, APP_ICON_CONTENT_DESC) + + /** Helper method providing default parameters to not clutter up the tests. */ + private fun transferToReceiverSucceeded(undoCallback: IUndoTransferCallback? = null) = + TransferToReceiverSucceeded( + 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, 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 ca909450c144..11b727ec507c 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,50 +31,94 @@ 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<MoveCloserToStartCast>() verify(controller).displayChip(capture(chipStateCaptor)) val chipState = chipStateCaptor.value!! - assertThat(chipState.otherDeviceName).isEqualTo(name) + assertThat(chipState.getChipTextString(context)).contains(name) } @Test fun closeToReceiverToEndCast_controllerTriggeredWithCorrectState() { val name = "Fake name" - callback.closeToReceiverToEndCast(mediaInfo, DeviceInfo(name)) + service.closeToReceiverToEndCast(mediaInfo, DeviceInfo(name)) val chipStateCaptor = argumentCaptor<MoveCloserToEndCast>() verify(controller).displayChip(capture(chipStateCaptor)) val chipState = chipStateCaptor.value!! - assertThat(chipState.otherDeviceName).isEqualTo(name) + assertThat(chipState.getChipTextString(context)).contains(name) + } + + @Test + fun transferToThisDeviceTriggered_controllerTriggeredWithCorrectState() { + service.transferToThisDeviceTriggered(mediaInfo, DeviceInfo("Fake name")) + + verify(controller).displayChip(any<TransferToThisDeviceTriggered>()) } @Test fun transferToReceiverTriggered_controllerTriggeredWithCorrectState() { val name = "Fake name" - callback.transferToReceiverTriggered(mediaInfo, DeviceInfo(name)) + service.transferToReceiverTriggered(mediaInfo, DeviceInfo(name)) val chipStateCaptor = argumentCaptor<TransferToReceiverTriggered>() verify(controller).displayChip(capture(chipStateCaptor)) val chipState = chipStateCaptor.value!! - assertThat(chipState.otherDeviceName).isEqualTo(name) + assertThat(chipState.getChipTextString(context)).contains(name) + } + + @Test + fun transferToReceiverSucceeded_controllerTriggeredWithCorrectState() { + val name = "Fake name" + val undoCallback = object : IUndoTransferCallback.Stub() { + override fun onUndoTriggered() {} + } + service.transferToReceiverSucceeded(mediaInfo, DeviceInfo(name), undoCallback) + + val chipStateCaptor = argumentCaptor<TransferToReceiverSucceeded>() + verify(controller).displayChip(capture(chipStateCaptor)) + + val chipState = chipStateCaptor.value!! + assertThat(chipState.getChipTextString(context)).contains(name) + 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<TransferToThisDeviceSucceeded>() + verify(controller).displayChip(capture(chipStateCaptor)) + + val chipState = chipStateCaptor.value!! + 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<TransferFailed>()) } + + @Test + fun noLongerCloseToReceiver_controllerRemoveChipTriggered() { + service.noLongerCloseToReceiver(mediaInfo, DeviceInfo("Fake name")) + + verify(controller).removeChip() + } } |