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

Commit a3a5fc8b authored by Caitlin Cassidy's avatar Caitlin Cassidy
Browse files

[Media TTT] Refactor media states to use an enum instead of sealed

classes.

There ended up being a lot of different constants associated with each
state and a lot of different maps or `when` functions in different files
to associate the constants with the states. It's more straightforward
to define an enum for each class. (And this will be even more true
when we add constants for the UiEvents.)

Bug: 203800663
Test: manual: verify adb commands still display all the different chip
states
Test: media.taptotransfer tests

Change-Id: I01d0ba2654d1f78fca1f697f81d0d6fa98aa2513
parent 8819133e
Loading
Loading
Loading
Loading
+30 −60
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.media.taptotransfer

import android.annotation.SuppressLint
import android.app.StatusBarManager
import android.content.Context
import android.media.MediaRoute2Info
@@ -23,16 +24,12 @@ import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.sender.AlmostCloseToEndCast
import com.android.systemui.media.taptotransfer.sender.AlmostCloseToStartCast
import com.android.systemui.media.taptotransfer.sender.TransferFailed
import com.android.systemui.media.taptotransfer.sender.TransferToReceiverTriggered
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.media.taptotransfer.receiver.ChipStateReceiver
import com.android.systemui.media.taptotransfer.sender.ChipStateSender
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import java.io.PrintWriter
import java.lang.IllegalArgumentException
import java.util.concurrent.Executor
import javax.inject.Inject

@@ -46,28 +43,6 @@ class MediaTttCommandLineHelper @Inject constructor(
    private val context: Context,
    @Main private val mainExecutor: Executor
) {
    /**
     * A map from a display state string typed in the command line to the display int it represents.
     */
    private val stateStringToStateInt: Map<String, Int> = mapOf(
        AlmostCloseToStartCast::class.simpleName!!
                to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
        AlmostCloseToEndCast::class.simpleName!!
                to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
        TransferToReceiverTriggered::class.simpleName!!
                to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
        TransferToThisDeviceTriggered::class.simpleName!!
                to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
        TransferToReceiverSucceeded::class.simpleName!!
                to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
        TransferToThisDeviceSucceeded::class.simpleName!!
                to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
        TransferFailed::class.simpleName!!
                to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
        FAR_FROM_RECEIVER_STATE
                to StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER
    )

    init {
        commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() }
        commandRegistry.registerCommand(RECEIVER_COMMAND) { ReceiverCommand() }
@@ -76,21 +51,23 @@ class MediaTttCommandLineHelper @Inject constructor(
    /** All commands for the sender device. */
    inner class SenderCommand : Command {
        override fun execute(pw: PrintWriter, args: List<String>) {
            val routeInfo = MediaRoute2Info.Builder("id", args[0])
                    .addFeature("feature")
                    .setPackageName(TEST_PACKAGE_NAME)
                    .build()

            val commandName = args[1]
            @StatusBarManager.MediaTransferSenderState
            val displayState = stateStringToStateInt[commandName]
            if (displayState == null) {
            val displayState: Int?
            try {
                displayState = ChipStateSender.getSenderStateIdFromName(commandName)
            }  catch (ex: IllegalArgumentException) {
                pw.println("Invalid command name $commandName")
                return
            }

            @SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE
            val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
                    as StatusBarManager
            val routeInfo = MediaRoute2Info.Builder("id", args[0])
                    .addFeature("feature")
                    .setPackageName(TEST_PACKAGE_NAME)
                    .build()
            statusBarManager.updateMediaTapToTransferSenderDisplay(
                    displayState,
                    routeInfo,
@@ -136,6 +113,17 @@ class MediaTttCommandLineHelper @Inject constructor(
    /** All commands for the receiver device. */
    inner class ReceiverCommand : Command {
        override fun execute(pw: PrintWriter, args: List<String>) {
            val commandName = args[0]
            @StatusBarManager.MediaTransferReceiverState
            val displayState: Int?
            try {
                displayState = ChipStateReceiver.getReceiverStateIdFromName(commandName)
            } catch (ex: IllegalArgumentException) {
                pw.println("Invalid command name $commandName")
                return
            }

            @SuppressLint("WrongConstant") // sysui is allowed to call STATUS_BAR_SERVICE
            val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
                    as StatusBarManager
            val routeInfo = MediaRoute2Info.Builder("id", "Test Name")
@@ -143,24 +131,12 @@ class MediaTttCommandLineHelper @Inject constructor(
                .setPackageName(TEST_PACKAGE_NAME)
                .build()

            when(val commandName = args[0]) {
                CLOSE_TO_SENDER_STATE ->
                    statusBarManager.updateMediaTapToTransferReceiverDisplay(
                        StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
                        routeInfo,
                        null,
                        null
                    )
                FAR_FROM_SENDER_STATE ->
            statusBarManager.updateMediaTapToTransferReceiverDisplay(
                        StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
                    displayState,
                    routeInfo,
                    null,
                    null
                )
                else ->
                    pw.println("Invalid command name $commandName")
            }
        }

        override fun help(pw: PrintWriter) {
@@ -173,11 +149,5 @@ class MediaTttCommandLineHelper @Inject constructor(
const val SENDER_COMMAND = "media-ttt-chip-sender"
@VisibleForTesting
const val RECEIVER_COMMAND = "media-ttt-chip-receiver"
@VisibleForTesting
const val FAR_FROM_RECEIVER_STATE = "FarFromReceiver"
@VisibleForTesting
const val CLOSE_TO_SENDER_STATE = "CloseToSender"
@VisibleForTesting
const val FAR_FROM_SENDER_STATE = "FarFromSender"
private const val CLI_TAG = "MediaTransferCli"
private const val TEST_PACKAGE_NAME = "com.android.systemui"
+30 −0
Original line number Diff line number Diff line
@@ -16,50 +16,15 @@

package com.android.systemui.media.taptotransfer.common

import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.util.Log

/**
 * A superclass chip state that will be subclassed by the sender chip and receiver chip.
 *
 * @property appPackageName the package name of the app playing the media. Will be used to fetch the
 *   app icon and app name.
 */
open class MediaTttChipState(
    internal val appPackageName: String?,
) {
    open fun getAppIcon(context: Context): Drawable? {
        appPackageName ?: return null
        return try {
            context.packageManager.getApplicationIcon(appPackageName)
        } catch (e: PackageManager.NameNotFoundException) {
            Log.w(TAG, "Cannot find icon for package $appPackageName", e)
            null
        }
    }

    /** Returns the name of the app playing the media or null if we can't find it. */
    open fun getAppName(context: Context): String? {
        appPackageName ?: return null
        return try {
            context.packageManager.getApplicationInfo(
                appPackageName, PackageManager.ApplicationInfoFlags.of(0)
            ).loadLabel(context.packageManager).toString()
        } catch (e: PackageManager.NameNotFoundException) {
            Log.w(TAG, "Cannot find name for package $appPackageName", e)
            null
        }
    }

interface ChipInfoCommon {
    /**
     * Returns the amount of time this chip should display on the screen before it times out and
     * disappears. [MediaTttChipControllerCommon] will ensure that the timeout resets each time we
     * receive a new state.
     * Returns the amount of time the given chip state should display on the screen before it times
     * out and disappears.
     */
    open fun getTimeoutMs(): Long = DEFAULT_TIMEOUT_MILLIS
    fun getTimeoutMs(): Long
}

private const val DEFAULT_TIMEOUT_MILLIS = 3000L
private val TAG = MediaTttChipState::class.simpleName!!
const val DEFAULT_TIMEOUT_MILLIS = 3000L
+49 −13
Original line number Diff line number Diff line
@@ -19,14 +19,16 @@ package com.android.systemui.media.taptotransfer.common
import android.annotation.LayoutRes
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import com.android.internal.widget.CachingIconView
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
@@ -40,8 +42,11 @@ import com.android.systemui.util.view.ViewUtil
 *
 * Subclasses need to override and implement [updateChipView], which is where they can control what
 * gets displayed to the user.
 *
 * The generic type T is expected to contain all the information necessary for the subclasses to
 * display the chip in a certain state, since they receive <T> in [updateChipView].
 */
abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
    internal val context: Context,
    internal val logger: MediaTttLogger,
    private val windowManager: WindowManager,
@@ -64,10 +69,10 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
    }

    /** The chip view currently being displayed. Null if the chip is not being displayed. */
    var chipView: ViewGroup? = null
    private var chipView: ViewGroup? = null

    /** A [Runnable] that, when run, will cancel the pending timeout of the chip. */
    var cancelChipViewTimeout: Runnable? = null
    private var cancelChipViewTimeout: Runnable? = null

    /**
     * Displays the chip with the current state.
@@ -75,7 +80,7 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
     * This method handles inflating and attaching the view, then delegates to [updateChipView] to
     * display the correct information in the chip.
     */
    fun displayChip(chipState: T) {
    fun displayChip(chipInfo: T) {
        val oldChipView = chipView
        if (chipView == null) {
            chipView = LayoutInflater
@@ -84,7 +89,7 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
        }
        val currentChipView = chipView!!

        updateChipView(chipState, currentChipView)
        updateChipView(chipInfo, currentChipView)

        // Add view if necessary
        if (oldChipView == null) {
@@ -96,7 +101,7 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
        cancelChipViewTimeout?.run()
        cancelChipViewTimeout = mainExecutor.executeDelayed(
            { removeChip(MediaTttRemovalReason.REASON_TIMEOUT) },
            chipState.getTimeoutMs()
            chipInfo.getTimeoutMs()
        )
    }

@@ -117,20 +122,28 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
    }

    /**
     * A method implemented by subclasses to update [currentChipView] based on [chipState].
     * A method implemented by subclasses to update [currentChipView] based on [chipInfo].
     */
    abstract fun updateChipView(chipState: T, currentChipView: ViewGroup)
    abstract fun updateChipView(chipInfo: T, currentChipView: ViewGroup)

    /**
     * An internal method to set the icon on the view.
     *
     * This is in the common superclass since both the sender and the receiver show an icon.
     *
     * @param appPackageName the package name of the app playing the media. Will be used to fetch
     *   the app icon and app name if overrides aren't provided.
     */
    internal fun setIcon(chipState: T, currentChipView: ViewGroup) {
    internal fun setIcon(
        currentChipView: ViewGroup,
        appPackageName: String?,
        appIconDrawableOverride: Drawable? = null,
        appNameOverride: CharSequence? = null,
    ) {
        val appIconView = currentChipView.requireViewById<CachingIconView>(R.id.app_icon)
        appIconView.contentDescription = chipState.getAppName(context)
        appIconView.contentDescription = appNameOverride ?: getAppName(appPackageName)

        val appIcon = chipState.getAppIcon(context)
        val appIcon = appIconDrawableOverride ?: getAppIcon(appPackageName)
        val visibility = if (appIcon != null) {
            View.VISIBLE
        } else {
@@ -140,6 +153,30 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
        appIconView.visibility = visibility
    }

    /** Returns the icon of the app playing the media or null if we can't find it. */
    private fun getAppIcon(appPackageName: String?): Drawable? {
        appPackageName ?: return null
        return try {
            context.packageManager.getApplicationIcon(appPackageName)
        } catch (e: PackageManager.NameNotFoundException) {
            Log.w(TAG, "Cannot find icon for package $appPackageName", e)
            null
        }
    }

    /** Returns the name of the app playing the media or null if we can't find it. */
    private fun getAppName(appPackageName: String?): String? {
        appPackageName ?: return null
        return try {
            context.packageManager.getApplicationInfo(
                    appPackageName, PackageManager.ApplicationInfoFlags.of(0)
            ).loadLabel(context.packageManager).toString()
        } catch (e: PackageManager.NameNotFoundException) {
            Log.w(TAG, "Cannot find name for package $appPackageName", e)
            null
        }
    }

    private fun onScreenTapped(e: MotionEvent) {
        val view = chipView ?: return
        // If the tap is within the chip bounds, we shouldn't hide the chip (in case users think the
@@ -159,4 +196,3 @@ object MediaTttRemovalReason {
    const val REASON_TIMEOUT = "TIMEOUT"
    const val REASON_SCREEN_TAP = "SCREEN_TAP"
}
+30 −24
Original line number Diff line number Diff line
@@ -16,35 +16,41 @@

package com.android.systemui.media.taptotransfer.receiver

import android.content.Context
import android.graphics.drawable.Drawable
import com.android.systemui.media.taptotransfer.common.MediaTttChipState
import android.app.StatusBarManager
import com.android.systemui.media.taptotransfer.common.DEFAULT_TIMEOUT_MILLIS
import com.android.systemui.media.taptotransfer.common.ChipInfoCommon

/**
 * A class that stores all the information necessary to display the media tap-to-transfer chip on
 * the receiver device.
 *
 * @property appIconDrawable a drawable representing the icon of the app playing the media. If
 *     present, this will be used in [this.getAppIcon] instead of [appPackageName].
 * @property appName a name for the app playing the media. If present, this will be used in
 *     [this.getAppName] instead of [appPackageName].
 */
class ChipStateReceiver(
    appPackageName: String?,
    private val appIconDrawable: Drawable?,
    private val appName: CharSequence?
) : MediaTttChipState(appPackageName) {
    override fun getAppIcon(context: Context): Drawable? {
        if (appIconDrawable != null) {
            return appIconDrawable
        }
        return super.getAppIcon(context)
    }
enum class ChipStateReceiver(
    @StatusBarManager.MediaTransferSenderState val stateInt: Int,
) {
    CLOSE_TO_SENDER(
        StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
    ),
    FAR_FROM_SENDER(
        StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
    );

    override fun getAppName(context: Context): String? {
        if (appName != null) {
            return appName.toString()
        }
        return super.getAppName(context)
    companion object {
        /**
         * Returns the receiver state enum associated with the given [displayState] from
         * [StatusBarManager].
         */
        fun getReceiverStateFromId(
            @StatusBarManager.MediaTransferReceiverState displayState: Int
        ) : ChipStateReceiver = values().first { it.stateInt == displayState }


        /**
         * Returns the state int from [StatusBarManager] associated with the given sender state
         * name.
         *
         * @param name the name of one of the [ChipStateReceiver] enums.
         */
        @StatusBarManager.MediaTransferReceiverState
        fun getReceiverStateIdFromName(name: String): Int = valueOf(name).stateInt
    }
}
+43 −35
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.media.taptotransfer.receiver

import android.app.StatusBarManager
import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
import android.os.Handler
@@ -27,6 +28,8 @@ import android.view.WindowManager
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.taptotransfer.common.ChipInfoCommon
import com.android.systemui.media.taptotransfer.common.DEFAULT_TIMEOUT_MILLIS
import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
@@ -50,7 +53,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
    mainExecutor: DelayableExecutor,
    tapGestureDetector: TapGestureDetector,
    @Main private val mainHandler: Handler,
) : MediaTttChipControllerCommon<ChipStateReceiver>(
) : MediaTttChipControllerCommon<ChipReceiverInfo>(
    context,
    logger,
    windowManager,
@@ -82,45 +85,50 @@ class MediaTttChipControllerReceiver @Inject constructor(
        appIcon: Icon?,
        appName: CharSequence?
    ) {
        logger.logStateChange(stateIntToString(displayState), routeInfo.id)
        when(displayState) {
            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER -> {
                val packageName = routeInfo.packageName
        val chipState: ChipStateReceiver? = ChipStateReceiver.getReceiverStateFromId(displayState)
        val stateName = chipState?.name ?: "Invalid"
        logger.logStateChange(stateName, routeInfo.id)

        if (chipState == null) {
            Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState")
            return
        }
        if (chipState == ChipStateReceiver.FAR_FROM_SENDER) {
            removeChip(removalReason = ChipStateReceiver.FAR_FROM_SENDER::class.simpleName!!)
            return
        }
        if (appIcon == null) {
                    displayChip(ChipStateReceiver(packageName, null, appName))
                } else {
            displayChip(ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appName))
            return
        }

        appIcon.loadDrawableAsync(
                context,
                Icon.OnDrawableLoadedListener { drawable ->
                            displayChip(
                                ChipStateReceiver(packageName, drawable, appName)
                            )},
                    displayChip(ChipReceiverInfo(routeInfo, drawable, appName))
                },
                // Notify the listener on the main handler since the listener will update
                // the UI.
                mainHandler
        )
    }
            }
            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER ->
                removeChip(removalReason = FAR_FROM_SENDER)
            else ->
                Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState")
        }
    }

    override fun updateChipView(chipState: ChipStateReceiver, currentChipView: ViewGroup) {
        setIcon(chipState, currentChipView)
    }

    private fun stateIntToString(@StatusBarManager.MediaTransferReceiverState state: Int): String {
        return when (state) {
            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER -> CLOSE_TO_SENDER
            StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER -> FAR_FROM_SENDER
            else -> "INVALID: $state"
    override fun updateChipView(chipInfo: ChipReceiverInfo, currentChipView: ViewGroup) {
        setIcon(
                currentChipView,
                chipInfo.routeInfo.packageName,
                chipInfo.appIconDrawableOverride,
                chipInfo.appNameOverride
        )
    }
}

data class ChipReceiverInfo(
    val routeInfo: MediaRoute2Info,
    val appIconDrawableOverride: Drawable?,
    val appNameOverride: CharSequence?
) : ChipInfoCommon {
    override fun getTimeoutMs() = DEFAULT_TIMEOUT_MILLIS
}

private const val RECEIVER_TAG = "MediaTapToTransferRcvr"
private const val CLOSE_TO_SENDER = "CLOSE_TO_SENDER"
private const val FAR_FROM_SENDER = "FAR_FROM_SENDER"
Loading