[Media TTT] Add app icon and name to the receiver SystemApi.
We typically get the app icon and name using `context.packageManager.getApplicationIcon(mediaRoute2Info.packageName)`. However, this may not work on the receiver device if the receiver doesn't have the app installed. The API will allow the caller to provide the app icon and name directly so that we can ensure the chip on the receiver device is displaying the necessary app information. Bug: 216318437 Bug: 217418566 Test: atest MediaTttChipControllerReceiverTest Test: CTS tests updated in different CL in same topic Change-Id: Ib5150355a08ae389b218500b235b4bd9c686f919
This commit is contained in:
@@ -911,7 +911,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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -117,12 +118,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));
|
||||
}
|
||||
|
||||
/** */
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -36,7 +36,7 @@ import com.android.systemui.R
|
||||
* gets displayed to the user.
|
||||
*/
|
||||
abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
|
||||
private val context: Context,
|
||||
internal val context: Context,
|
||||
private val windowManager: WindowManager,
|
||||
@LayoutRes private val chipLayoutRes: Int
|
||||
) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ChipStateReceiver>(
|
||||
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")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ImageView>(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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user