diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 1adaf4b82ea78..321ebcf42ea56 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -914,7 +914,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public int getNavBarModeOverride(); method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean); method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setNavBarModeOverride(int); - method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay(int, @NonNull android.media.MediaRoute2Info); + method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay(int, @NonNull android.media.MediaRoute2Info, @Nullable android.graphics.drawable.Icon, @Nullable CharSequence); method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferSenderDisplay(int, @NonNull android.media.MediaRoute2Info, @Nullable java.util.concurrent.Executor, @Nullable Runnable); field public static final int MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER = 0; // 0x0 field public static final int MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER = 1; // 0x1 diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index e8b5c0ddca8cf..5f0034285f0ac 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -1012,6 +1012,8 @@ public class StatusBarManager { * * @param displayState the new state for media tap-to-transfer. * @param routeInfo the media route information for the media being transferred. + * @param appIcon the icon of the app playing the media. + * @param appName the name of the app playing the media. * * @hide */ @@ -1019,11 +1021,13 @@ public class StatusBarManager { @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL) public void updateMediaTapToTransferReceiverDisplay( @MediaTransferReceiverState int displayState, - @NonNull MediaRoute2Info routeInfo) { + @NonNull MediaRoute2Info routeInfo, + @Nullable Icon appIcon, + @Nullable CharSequence appName) { Objects.requireNonNull(routeInfo); IStatusBarService svc = getService(); try { - svc.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo); + svc.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo, appIcon, appName); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 51eb4296d7aee..46f54ce5ef59a 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -308,5 +308,7 @@ oneway interface IStatusBar /** Notifies System UI about an update to the media tap-to-transfer receiver state. */ void updateMediaTapToTransferReceiverDisplay( int displayState, - in MediaRoute2Info routeInfo); + in MediaRoute2Info routeInfo, + in Icon appIcon, + in CharSequence appName); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 0c45e5b3eab4b..6c17df1295b2c 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -208,5 +208,7 @@ interface IStatusBarService /** Notifies System UI about an update to the media tap-to-transfer receiver state. */ void updateMediaTapToTransferReceiverDisplay( int displayState, - in MediaRoute2Info routeInfo); + in MediaRoute2Info routeInfo, + in Icon appIcon, + in CharSequence appName); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index f9333a3e157d9..3225f73adb82a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -18,6 +18,7 @@ package com.android.systemui.media.dagger; import android.app.Service; import android.content.Context; +import android.os.Handler; import android.view.WindowManager; import com.android.systemui.dagger.SysUISingleton; @@ -119,12 +120,14 @@ public interface MediaModule { MediaTttFlags mediaTttFlags, CommandQueue commandQueue, Context context, - WindowManager windowManager) { + WindowManager windowManager, + @Main Handler mainHandler) { if (!mediaTttFlags.isMediaTttEnabled()) { return Optional.empty(); } return Optional.of( - new MediaTttChipControllerReceiver(commandQueue, context, windowManager)); + new MediaTttChipControllerReceiver( + commandQueue, context, windowManager, mainHandler)); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt index 0968c924db8e8..9dd8222ff6a19 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt @@ -147,12 +147,16 @@ class MediaTttCommandLineHelper @Inject constructor( CLOSE_TO_SENDER_STATE -> statusBarManager.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, - routeInfo + routeInfo, + null, + null ) FAR_FROM_SENDER_STATE -> statusBarManager.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, - routeInfo + routeInfo, + null, + null ) else -> pw.println("Invalid command name $commandName") diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt index f11e5cf63caa4..4993105fa681d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt @@ -36,7 +36,7 @@ import com.android.systemui.R * gets displayed to the user. */ abstract class MediaTttChipControllerCommon( - private val context: Context, + internal val context: Context, private val windowManager: WindowManager, @LayoutRes private val chipLayoutRes: Int ) { diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt index a528d69630729..2da48cef792a4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipState.kt @@ -30,7 +30,7 @@ import android.util.Log open class MediaTttChipState( internal val appPackageName: String?, ) { - fun getAppIcon(context: Context): Drawable? { + open fun getAppIcon(context: Context): Drawable? { appPackageName ?: return null return try { context.packageManager.getApplicationIcon(appPackageName) @@ -41,7 +41,7 @@ open class MediaTttChipState( } /** Returns the name of the app playing the media or null if we can't find it. */ - fun getAppName(context: Context): String? { + open fun getAppName(context: Context): String? { appPackageName ?: return null return try { context.packageManager.getApplicationInfo( diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt index 61d3243a2dc6e..6a4b62a8c1aeb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt @@ -16,12 +16,35 @@ package com.android.systemui.media.taptotransfer.receiver +import android.content.Context +import android.graphics.drawable.Drawable import com.android.systemui.media.taptotransfer.common.MediaTttChipState /** * 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?, -) : MediaTttChipState(appPackageName) + 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) + } + + override fun getAppName(context: Context): String? { + if (appName != null) { + return appName.toString() + } + return super.getAppName(context) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 277553addb1eb..18623c0686fde 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -18,12 +18,17 @@ 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 import android.util.Log import android.view.ViewGroup import android.view.WindowManager import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon import com.android.systemui.statusbar.CommandQueue import javax.inject.Inject @@ -38,16 +43,19 @@ class MediaTttChipControllerReceiver @Inject constructor( commandQueue: CommandQueue, context: Context, windowManager: WindowManager, + @Main private val mainHandler: Handler, ) : MediaTttChipControllerCommon( context, windowManager, R.layout.media_ttt_chip_receiver ) { private val commandQueueCallbacks = object : CommandQueue.Callbacks { override fun updateMediaTapToTransferReceiverDisplay( @StatusBarManager.MediaTransferReceiverState displayState: Int, - routeInfo: MediaRoute2Info + routeInfo: MediaRoute2Info, + appIcon: Icon?, + appName: CharSequence? ) { this@MediaTttChipControllerReceiver.updateMediaTapToTransferReceiverDisplay( - displayState, routeInfo + displayState, routeInfo, appIcon, appName ) } } @@ -58,11 +66,28 @@ class MediaTttChipControllerReceiver @Inject constructor( private fun updateMediaTapToTransferReceiverDisplay( @StatusBarManager.MediaTransferReceiverState displayState: Int, - routeInfo: MediaRoute2Info + routeInfo: MediaRoute2Info, + appIcon: Icon?, + appName: CharSequence? ) { when(displayState) { - StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER -> - displayChip(ChipStateReceiver(routeInfo.packageName)) + StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER -> { + val packageName = routeInfo.packageName + if (appIcon == null) { + displayChip(ChipStateReceiver(packageName, null, appName)) + } else { + appIcon.loadDrawableAsync( + context, + Icon.OnDrawableLoadedListener { drawable -> + displayChip( + ChipStateReceiver(packageName, 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() else -> Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 2f5eaa6212831..b355b0551abec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -453,7 +453,9 @@ public class CommandQueue extends IStatusBar.Stub implements /** @see IStatusBar#updateMediaTapToTransferReceiverDisplay */ default void updateMediaTapToTransferReceiverDisplay( @StatusBarManager.MediaTransferReceiverState int displayState, - @NonNull MediaRoute2Info routeInfo) {} + @NonNull MediaRoute2Info routeInfo, + @Nullable Icon appIcon, + @Nullable CharSequence appName) {} } public CommandQueue(Context context) { @@ -1208,10 +1210,14 @@ public class CommandQueue extends IStatusBar.Stub implements @Override public void updateMediaTapToTransferReceiverDisplay( int displayState, - MediaRoute2Info routeInfo) { + @NonNull MediaRoute2Info routeInfo, + @Nullable Icon appIcon, + @Nullable CharSequence appName) { SomeArgs args = SomeArgs.obtain(); args.arg1 = displayState; args.arg2 = routeInfo; + args.arg3 = appIcon; + args.arg4 = appName; mHandler.obtainMessage(MSG_MEDIA_TRANSFER_RECEIVER_STATE, args).sendToTarget(); } @@ -1629,9 +1635,11 @@ public class CommandQueue extends IStatusBar.Stub implements args = (SomeArgs) msg.obj; int receiverDisplayState = (int) args.arg1; MediaRoute2Info receiverRouteInfo = (MediaRoute2Info) args.arg2; + Icon appIcon = (Icon) args.arg3; + appName = (CharSequence) args.arg4; for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).updateMediaTapToTransferReceiverDisplay( - receiverDisplayState, receiverRouteInfo); + receiverDisplayState, receiverRouteInfo, appIcon, appName); } args.recycle(); break; diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt index cb05d03026985..14afecebd1f63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt @@ -203,7 +203,9 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { verify(statusBarManager).updateMediaTapToTransferReceiverDisplay( eq(StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER), - any() + any(), + nullable(), + nullable() ) } @@ -213,7 +215,9 @@ class MediaTttCommandLineHelperTest : SysuiTestCase() { verify(statusBarManager).updateMediaTapToTransferReceiverDisplay( eq(StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER), - any() + any(), + nullable(), + nullable() ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index 56a719085b39a..44f691c3f6982 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -17,7 +17,9 @@ package com.android.systemui.media.taptotransfer.receiver import android.app.StatusBarManager +import android.graphics.drawable.Icon import android.media.MediaRoute2Info +import android.os.Handler import android.view.View import android.view.ViewGroup import android.view.WindowManager @@ -51,7 +53,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - controllerReceiver = MediaTttChipControllerReceiver(commandQueue, context, windowManager) + controllerReceiver = MediaTttChipControllerReceiver( + commandQueue, context, windowManager, Handler.getMain()) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) verify(commandQueue).addCallback(callbackCaptor.capture()) @@ -60,19 +63,24 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Test fun commandQueueCallback_closeToSender_triggersChip() { + val appName = "FakeAppName" commandQueueCallback.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, - routeInfo + routeInfo, + /* appIcon= */ null, + appName ) - assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(ROUTE_NAME) + assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(appName) } @Test fun commandQueueCallback_farFromSender_noChipShown() { commandQueueCallback.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, - routeInfo + routeInfo, + null, + null ) verify(windowManager, never()).addView(any(), any()) @@ -82,12 +90,16 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { fun commandQueueCallback_closeThenFar_chipShownThenHidden() { commandQueueCallback.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER, - routeInfo + routeInfo, + null, + null ) commandQueueCallback.updateMediaTapToTransferReceiverDisplay( StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER, - routeInfo + routeInfo, + null, + null ) val viewCaptor = ArgumentCaptor.forClass(View::class.java) @@ -96,16 +108,45 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { } @Test - fun displayChip_chipContainsIcon() { - val state = ChipStateReceiver(PACKAGE_NAME) + fun displayChip_nullAppIconDrawable_iconIsFromPackageName() { + val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, "appName") controllerReceiver.displayChip(state) assertThat(getChipView().getAppIconView().drawable).isEqualTo(state.getAppIcon(context)) + + } + + @Test + fun displayChip_hasAppIconDrawable_iconIsDrawable() { + val drawable = Icon.createWithResource(context, R.drawable.ic_cake).loadDrawable(context) + val state = ChipStateReceiver(PACKAGE_NAME, drawable, "appName") + + controllerReceiver.displayChip(state) + + assertThat(getChipView().getAppIconView().drawable).isEqualTo(drawable) + } + + @Test + fun displayChip_nullAppName_iconContentDescriptionIsFromPackageName() { + val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, appName = null) + + controllerReceiver.displayChip(state) + assertThat(getChipView().getAppIconView().contentDescription) .isEqualTo(state.getAppName(context)) } + @Test + fun displayChip_hasAppName_iconContentDescriptionIsAppNameOverride() { + val appName = "FakeAppName" + val state = ChipStateReceiver(PACKAGE_NAME, appIconDrawable = null, appName) + + controllerReceiver.displayChip(state) + + assertThat(getChipView().getAppIconView().contentDescription).isEqualTo(appName) + } + private fun getChipView(): ViewGroup { val viewCaptor = ArgumentCaptor.forClass(View::class.java) verify(windowManager).addView(viewCaptor.capture(), any()) @@ -115,10 +156,9 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { private fun ViewGroup.getAppIconView() = this.requireViewById(R.id.app_icon) } -private const val ROUTE_NAME = "Test name" private const val PACKAGE_NAME = "com.android.systemui" -private val routeInfo = MediaRoute2Info.Builder("id", ROUTE_NAME) +private val routeInfo = MediaRoute2Info.Builder("id", "Test route name") .addFeature("feature") .setPackageName(PACKAGE_NAME) .build() diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 94f483c65eb53..2049f3d3be245 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -2031,11 +2031,14 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void updateMediaTapToTransferReceiverDisplay( @StatusBarManager.MediaTransferReceiverState int displayState, - MediaRoute2Info routeInfo) { + MediaRoute2Info routeInfo, + @Nullable Icon appIcon, + @Nullable CharSequence appName) { enforceMediaContentControl(); if (mBar != null) { try { - mBar.updateMediaTapToTransferReceiverDisplay(displayState, routeInfo); + mBar.updateMediaTapToTransferReceiverDisplay( + displayState, routeInfo, appIcon, appName); } catch (RemoteException e) { Slog.e(TAG, "updateMediaTapToTransferReceiverDisplay", e); }