blob: bde588c14fc83badad51fb3b81771e63b9306351 [file] [log] [blame]
/*
* Copyright (C) 2021 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.media.taptotransfer.sender
import android.app.StatusBarManager
import android.content.Context
import android.media.MediaRoute2Info
import android.util.Log
import android.view.View
import androidx.annotation.StringRes
import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
/**
* A class enumerating all the possible states of the media tap-to-transfer chip on the sender
* device.
*
* @property stateInt the integer from [StatusBarManager] corresponding with this state.
* @property stringResId the res ID of the string that should be displayed in the chip. Null if the
* state should not have the chip be displayed.
* @property isMidTransfer true if the state represents that a transfer is currently ongoing.
* @property isTransferFailure true if the state represents that the transfer has failed.
* @property timeout the amount of time this chip should display on the screen before it times out
* and disappears.
*/
enum class ChipStateSender(
@StatusBarManager.MediaTransferSenderState val stateInt: Int,
val uiEvent: UiEventLogger.UiEventEnum,
@StringRes val stringResId: Int?,
val isMidTransfer: Boolean = false,
val isTransferFailure: Boolean = false,
val timeout: Long = DEFAULT_TIMEOUT_MILLIS
) {
/**
* 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.
*/
ALMOST_CLOSE_TO_START_CAST(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST,
R.string.media_move_closer_to_start_cast,
),
/**
* 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).
*/
ALMOST_CLOSE_TO_END_CAST(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST,
R.string.media_move_closer_to_end_cast,
),
/**
* A state representing that a transfer to the receiver device has been initiated (but not
* completed).
*/
TRANSFER_TO_RECEIVER_TRIGGERED(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED,
R.string.media_transfer_playing_different_device,
isMidTransfer = true,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
),
/**
* A state representing that a transfer from the receiver device and back to this device (the
* sender) has been initiated (but not completed).
*/
TRANSFER_TO_THIS_DEVICE_TRIGGERED(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
R.string.media_transfer_playing_this_device,
isMidTransfer = true,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
),
/**
* A state representing that a transfer to the receiver device has been successfully completed.
*/
TRANSFER_TO_RECEIVER_SUCCEEDED(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED,
R.string.media_transfer_playing_different_device
) {
override fun undoClickListener(
controllerSender: MediaTttChipControllerSender,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
uiEventLogger: MediaTttSenderUiEventLogger
): View.OnClickListener? {
if (undoCallback == null) {
return null
}
return View.OnClickListener {
uiEventLogger.logUndoClicked(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED
)
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 ast o why the UI hasn't changed yet. So, we immediately change the UI
// here.
controllerSender.displayView(
ChipSenderInfo(
TRANSFER_TO_THIS_DEVICE_TRIGGERED, routeInfo, undoCallback
)
)
}
}
},
/**
* A state representing that a transfer back to this device has been successfully completed.
*/
TRANSFER_TO_THIS_DEVICE_SUCCEEDED(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
R.string.media_transfer_playing_this_device
) {
override fun undoClickListener(
controllerSender: MediaTttChipControllerSender,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
uiEventLogger: MediaTttSenderUiEventLogger
): View.OnClickListener? {
if (undoCallback == null) {
return null
}
return View.OnClickListener {
uiEventLogger.logUndoClicked(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED
)
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.displayView(
ChipSenderInfo(
TRANSFER_TO_RECEIVER_TRIGGERED, routeInfo, undoCallback
)
)
}
}
},
/** A state representing that a transfer to the receiver device has failed. */
TRANSFER_TO_RECEIVER_FAILED(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED,
R.string.media_transfer_failed,
isTransferFailure = true
),
/** A state representing that a transfer back to this device has failed. */
TRANSFER_TO_THIS_DEVICE_FAILED(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED,
R.string.media_transfer_failed,
isTransferFailure = true
),
/** A state representing that this device is far away from any receiver device. */
FAR_FROM_RECEIVER(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_FAR_FROM_RECEIVER,
stringResId = null
);
/**
* Returns a fully-formed string with the text that the chip should display.
*
* @param otherDeviceName the name of the other device involved in the transfer.
*/
fun getChipTextString(context: Context, otherDeviceName: String): String? {
if (stringResId == null) {
return null
}
return context.getString(stringResId, otherDeviceName)
}
/**
* 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.
* @param 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.
*/
open fun undoClickListener(
controllerSender: MediaTttChipControllerSender,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
uiEventLogger: MediaTttSenderUiEventLogger
): View.OnClickListener? = null
companion object {
/**
* Returns the sender state enum associated with the given [displayState] from
* [StatusBarManager].
*/
fun getSenderStateFromId(
@StatusBarManager.MediaTransferSenderState displayState: Int,
): ChipStateSender? = try {
values().first { it.stateInt == displayState }
} catch (e: NoSuchElementException) {
Log.e(TAG, "Could not find requested state $displayState", e)
null
}
/**
* Returns the state int from [StatusBarManager] associated with the given sender state
* name.
*
* @param name the name of one of the [ChipStateSender] enums.
*/
@StatusBarManager.MediaTransferSenderState
fun getSenderStateIdFromName(name: String): Int = valueOf(name).stateInt
}
}
// Give the Transfer*Triggered states a longer timeout since those states represent an active
// process and we should keep the user informed about it as long as possible (but don't allow it to
// continue indefinitely).
private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000L
private const val TAG = "ChipStateSender"