Loading packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +30 −60 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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() } Loading @@ -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, Loading Loading @@ -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") Loading @@ -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) { Loading @@ -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" packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt→packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt +30 −0 Original line number Diff line number Diff line Loading @@ -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 packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +49 −13 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading @@ -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. Loading @@ -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 Loading @@ -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) { Loading @@ -96,7 +101,7 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>( cancelChipViewTimeout?.run() cancelChipViewTimeout = mainExecutor.executeDelayed( { removeChip(MediaTttRemovalReason.REASON_TIMEOUT) }, chipState.getTimeoutMs() chipInfo.getTimeoutMs() ) } Loading @@ -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 { Loading @@ -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 Loading @@ -159,4 +196,3 @@ object MediaTttRemovalReason { const val REASON_TIMEOUT = "TIMEOUT" const val REASON_SCREEN_TAP = "SCREEN_TAP" } packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt +30 −24 Original line number Diff line number Diff line Loading @@ -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 } } packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +43 −35 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -50,7 +53,7 @@ class MediaTttChipControllerReceiver @Inject constructor( mainExecutor: DelayableExecutor, tapGestureDetector: TapGestureDetector, @Main private val mainHandler: Handler, ) : MediaTttChipControllerCommon<ChipStateReceiver>( ) : MediaTttChipControllerCommon<ChipReceiverInfo>( context, logger, windowManager, Loading Loading @@ -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
packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +30 −60 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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() } Loading @@ -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, Loading Loading @@ -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") Loading @@ -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) { Loading @@ -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"
packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt→packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt +30 −0 Original line number Diff line number Diff line Loading @@ -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
packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +49 −13 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading @@ -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. Loading @@ -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 Loading @@ -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) { Loading @@ -96,7 +101,7 @@ abstract class MediaTttChipControllerCommon<T : MediaTttChipState>( cancelChipViewTimeout?.run() cancelChipViewTimeout = mainExecutor.executeDelayed( { removeChip(MediaTttRemovalReason.REASON_TIMEOUT) }, chipState.getTimeoutMs() chipInfo.getTimeoutMs() ) } Loading @@ -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 { Loading @@ -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 Loading @@ -159,4 +196,3 @@ object MediaTttRemovalReason { const val REASON_TIMEOUT = "TIMEOUT" const val REASON_SCREEN_TAP = "SCREEN_TAP" }
packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt +30 −24 Original line number Diff line number Diff line Loading @@ -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 } }
packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +43 −35 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -50,7 +53,7 @@ class MediaTttChipControllerReceiver @Inject constructor( mainExecutor: DelayableExecutor, tapGestureDetector: TapGestureDetector, @Main private val mainHandler: Handler, ) : MediaTttChipControllerCommon<ChipStateReceiver>( ) : MediaTttChipControllerCommon<ChipReceiverInfo>( context, logger, windowManager, Loading Loading @@ -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"