blob: bb7bc6fff99fa0f172a24e7fe95b8e9bea2be6b6 [file] [log] [blame]
/*
* 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.media.taptotransfer.sender
import android.app.StatusBarManager
import android.content.Context
import android.media.MediaRoute2Info
import android.view.View
import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.CoreStartable
import com.android.systemui.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
import javax.inject.Inject
/**
* A coordinator for showing/hiding the Media Tap-To-Transfer UI on the **sending** device. This UI
* is shown when a user is transferring media to/from this device and a receiver device.
*/
@SysUISingleton
class MediaTttSenderCoordinator
@Inject
constructor(
private val chipbarCoordinator: ChipbarCoordinator,
private val commandQueue: CommandQueue,
private val context: Context,
@MediaTttSenderLogger private val logger: MediaTttLogger,
private val mediaTttFlags: MediaTttFlags,
private val uiEventLogger: MediaTttSenderUiEventLogger,
) : CoreStartable {
private var displayedState: ChipStateSender? = null
private val commandQueueCallbacks =
object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferSenderDisplay(
@StatusBarManager.MediaTransferSenderState displayState: Int,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?
) {
this@MediaTttSenderCoordinator.updateMediaTapToTransferSenderDisplay(
displayState,
routeInfo,
undoCallback
)
}
}
override fun start() {
if (mediaTttFlags.isMediaTttEnabled()) {
commandQueue.addCallback(commandQueueCallbacks)
}
}
private fun updateMediaTapToTransferSenderDisplay(
@StatusBarManager.MediaTransferSenderState displayState: Int,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?
) {
val chipState: ChipStateSender? = ChipStateSender.getSenderStateFromId(displayState)
val stateName = chipState?.name ?: "Invalid"
logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
if (chipState == null) {
logger.logStateChangeError(displayState)
return
}
uiEventLogger.logSenderStateChange(chipState)
if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
// Return early if we're not displaying a chip anyway
val currentDisplayedState = displayedState ?: return
val removalReason = ChipStateSender.FAR_FROM_RECEIVER.name
if (
currentDisplayedState.transferStatus == TransferStatus.IN_PROGRESS ||
currentDisplayedState.transferStatus == TransferStatus.SUCCEEDED
) {
// Don't remove the chip if we're in progress or succeeded, since the user should
// still be able to see the status of the transfer.
logger.logRemovalBypass(
removalReason,
bypassReason = "transferStatus=${currentDisplayedState.transferStatus.name}"
)
return
}
displayedState = null
chipbarCoordinator.removeView(routeInfo.id, removalReason)
} else {
displayedState = chipState
chipbarCoordinator.displayView(
createChipbarInfo(
chipState,
routeInfo,
undoCallback,
context,
logger,
)
)
}
}
/**
* Creates an instance of [ChipbarInfo] that can be sent to [ChipbarCoordinator] for display.
*/
private fun createChipbarInfo(
chipStateSender: ChipStateSender,
routeInfo: MediaRoute2Info,
undoCallback: IUndoMediaTransferCallback?,
context: Context,
logger: MediaTttLogger,
): ChipbarInfo {
val packageName = routeInfo.clientPackageName
val otherDeviceName = routeInfo.name.toString()
return ChipbarInfo(
// Display the app's icon as the start icon
startIcon = MediaTttUtils.getIconFromPackageName(context, packageName, logger),
text = chipStateSender.getChipTextString(context, otherDeviceName),
endItem =
when (chipStateSender.endItem) {
null -> null
is SenderEndItem.Loading -> ChipbarEndItem.Loading
is SenderEndItem.Error -> ChipbarEndItem.Error
is SenderEndItem.UndoButton -> {
if (undoCallback != null) {
getUndoButton(
undoCallback,
chipStateSender.endItem.uiEventOnClick,
chipStateSender.endItem.newState,
routeInfo,
)
} else {
null
}
}
},
vibrationEffect = chipStateSender.transferStatus.vibrationEffect,
windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
timeoutMs = chipStateSender.timeout,
id = routeInfo.id,
)
}
/**
* Returns an undo button for the chip.
*
* When the button is clicked: [undoCallback] will be triggered, [uiEvent] will be logged, and
* this coordinator will transition to [newState].
*/
private fun getUndoButton(
undoCallback: IUndoMediaTransferCallback,
uiEvent: UiEventLogger.UiEventEnum,
@StatusBarManager.MediaTransferSenderState newState: Int,
routeInfo: MediaRoute2Info,
): ChipbarEndItem.Button {
val onClickListener =
View.OnClickListener {
uiEventLogger.logUndoClicked(uiEvent)
undoCallback.onUndoTriggered()
// The external service should eventually send us a new TransferTriggered state, but
// 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.
updateMediaTapToTransferSenderDisplay(
newState,
routeInfo,
// Since we're force-updating the UI, we don't have any [undoCallback] from the
// external service (and TransferTriggered states don't have undo callbacks
// anyway).
undoCallback = null,
)
}
return ChipbarEndItem.Button(
Text.Resource(R.string.media_transfer_undo),
onClickListener,
)
}
}