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.
This commit is contained in:
Caitlin Cassidy
2022-01-24 20:04:34 +00:00
committed by Android (Google) Code Review
12 changed files with 674 additions and 217 deletions

View File

@@ -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>

View File

@@ -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
@@ -72,6 +73,50 @@ 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 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.
*
@@ -79,4 +124,10 @@ interface IDeviceSenderCallback {
* 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);
}

View File

@@ -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();
}

View File

@@ -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));
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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 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.
* A state representing that a transfer from the receiver device and back to this device (the
* sender) has been initiated (but not completed).
*/
class TransferSucceeded(
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,
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_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 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. */
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)
}
}

View File

@@ -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

View File

@@ -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()
}
}

View File

@@ -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
@@ -50,12 +50,10 @@ 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
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)
@@ -146,10 +132,35 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() {
}
@Test
fun sender_transferSucceeded_chipDisplayWithCorrectState() {
commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
fun sender_transferToThisDeviceTriggered_chipDisplayWithCorrectState() {
commandRegistry.onShellCommand(pw, getTransferToThisDeviceTriggeredCommand())
verify(mediaTttChipControllerSender).displayChip(any(TransferSucceeded::class.java))
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()
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(
ADD_CHIP_COMMAND_SENDER_TAG,
SENDER_COMMAND,
DEVICE_NAME,
TRANSFER_SUCCEEDED_COMMAND_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(
SENDER_COMMAND,
DEVICE_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>) {
}

View File

@@ -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()).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 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 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()).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(transferSucceeded(undoRunnable = runnable))
controllerSender.displayChip(transferToReceiverSucceeded(undoCallback))
getChipView().getUndoButton().performClick()
assertThat(runnableRun).isTrue()
assertThat(undoCallbackCalled).isTrue()
}
@Test
fun transferFailed_appIcon_noDeviceName_noLoadingIcon_noUndo_failureIcon() {
controllerSender.displayChip(transferFailed())
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
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()).doesNotContain(DEVICE_NAME)
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()
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.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 transferFailed() =
TransferFailed(appIconDrawable, APP_ICON_CONTENT_DESC, DEVICE_NAME)
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)
}
private const val DEVICE_NAME = "My Tablet"

View File

@@ -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()
}
}