Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5852540a authored by Caitlin Cassidy's avatar Caitlin Cassidy
Browse files

[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.

Bug: 203800643
Bug: 203800347
Test: verify `adb shell cmd statusbar media-ttt-chip-add-sender Device
TransferToReceiverSucceeded` shows a chip with an Undo button. Verify
tapping the undo button switches the chip to the loading state again.
Test: media.taptotransfer tests

Change-Id: I912caa4408badffaa7a8e19f04e89bafd5d9a8ad
parent a6d18d89
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
@@ -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
@@ -92,9 +93,13 @@ interface IDeviceSenderCallback {
     *   - 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 MediaRoute2Info mediaInfo,
        in DeviceInfo otherDeviceInfo,
        in IUndoTransferCallback undoCallback);

    /**
     * Invoke to notify System UI that the attempted transfer has failed.
+32 −0
Original line number Diff line number Diff line
/*
 * 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();
}
+42 −29
Original line number Diff line number Diff line
@@ -39,7 +39,8 @@ import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTrigger
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
@@ -56,7 +57,7 @@ class MediaTttCommandLineHelper @Inject constructor(
    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 =
@@ -85,33 +86,45 @@ 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)
                    }
                }
                TRANSFER_TO_THIS_DEVICE_TRIGGERED_COMMAND_NAME -> {
                    runOnService { senderCallback ->
                        senderCallback.transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo)
                    runOnService { senderService ->
                        senderService.transferToThisDeviceTriggered(mediaInfo, otherDeviceInfo)
                    }
                }
                TRANSFER_TO_RECEIVER_SUCCEEDED_COMMAND_NAME -> {
                    runOnService { senderCallback ->
                        senderCallback.transferToReceiverSucceeded(mediaInfo, otherDeviceInfo)
                    val undoCallback = object : IUndoTransferCallback.Stub() {
                        override fun onUndoTriggered() {
                            Log.i(TAG, "Undo 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_FAILED_COMMAND_NAME -> {
                    runOnService { senderCallback ->
                        senderCallback.transferFailed(mediaInfo, otherDeviceInfo)
                    runOnService { senderService ->
                        senderService.transferFailed(mediaInfo, otherDeviceInfo)
                    }
                }
                else -> {
@@ -133,16 +146,16 @@ class MediaTttCommandLineHelper @Inject constructor(
            )
        }

        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),
@@ -157,7 +170,7 @@ class MediaTttCommandLineHelper @Inject constructor(
    inner class RemoveChipCommandSender : Command {
        override fun execute(pw: PrintWriter, args: List<String>) {
            mediaTttChipControllerSender.removeChip()
            if (senderCallback != null) {
            if (senderService != null) {
                context.unbindService(senderServiceConnection)
            }
        }
@@ -188,27 +201,27 @@ 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)
    /** 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)
    }
}

+4 −3
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.Context
import android.graphics.drawable.Drawable
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
@@ -114,14 +115,14 @@ class TransferToThisDeviceTriggered(
 * 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 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 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 undoRunnable: Runnable? = null
    val undoCallback: IUndoTransferCallback? = null
) : ChipStateSender(appIconDrawable, appIconContentDescription) {
    override fun getChipTextString(context: Context): String {
        return context.getString(R.string.media_transfer_playing_different_device, otherDeviceName)
+14 −2
Original line number Diff line number Diff line
@@ -54,8 +54,20 @@ class MediaTttChipControllerSender @Inject constructor(

        // Undo
        val undoClickListener: View.OnClickListener? =
            if (chipState is TransferToReceiverSucceeded && chipState.undoRunnable != null)
                View.OnClickListener { chipState.undoRunnable.run() }
            if (chipState is TransferToReceiverSucceeded && chipState.undoCallback != null)
                View.OnClickListener {
                    chipState.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.
                    displayChip(
                        TransferToThisDeviceTriggered(
                            chipState.appIconDrawable,
                            chipState.appIconContentDescription
                        )
                    )
                }
            else
                null
        val undoView = currentChipView.requireViewById<View>(R.id.undo)
Loading