Merge "[Media TTT] Add app icon and name to the receiver SystemApi."

This commit is contained in:
Caitlin Cassidy
2022-02-10 21:35:27 +00:00
committed by Android (Google) Code Review
14 changed files with 153 additions and 35 deletions

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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));
}
/** */

View File

@@ -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")

View File

@@ -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
) {

View File

@@ -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(

View File

@@ -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)
}
}

View File

@@ -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")

View File

@@ -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;

View File

@@ -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()
)
}

View File

@@ -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()

View File

@@ -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);
}