diff options
author | 2022-02-08 12:56:42 -0500 | |
---|---|---|
committer | 2022-02-10 11:00:00 -0500 | |
commit | ded4a734d163ed022e84940c3786cf99a80b0f06 (patch) | |
tree | 096b13fb1f0ad07b938956ee97947ffb3c1d1962 | |
parent | 4353b0b03fbe8f18cd1c994b95791c08a88e9848 (diff) |
Add SystemAPI to override default media output switcher
To support RCN use case, allow system apps with the
MEDIA_CONTENT_CONTROL permission to declare that their MediaStyle notification
is for remote playback, and pass in their own device name, icon, and intent to
use in place of the default output switcher that appears on the media controls
Bug: 204910409
Test: atest
Test: manual with test app
Change-Id: Icbfa288c48da9c2ab2fdfdd6b0bfe2b545afaac0
12 files changed, 244 insertions, 41 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index e1d723aa480f..f478fc50787f 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -848,6 +848,10 @@ package android.app { field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb } + public static class Notification.MediaStyle extends android.app.Notification.Style { + method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.app.Notification.MediaStyle setRemotePlaybackInfo(@NonNull CharSequence, @DrawableRes int, @Nullable android.app.PendingIntent); + } + public static final class Notification.TvExtender implements android.app.Notification.Extender { ctor public Notification.TvExtender(); ctor public Notification.TvExtender(android.app.Notification); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 4132c64a44c9..5fd5d317b8d6 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -299,6 +299,9 @@ package android.app { public class Notification implements android.os.Parcelable { method public boolean shouldShowForegroundImmediately(); + field public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice"; + field public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon"; + field public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent"; } public final class NotificationChannel implements android.os.Parcelable { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 6a147720f8e9..78db59b5c13b 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1330,6 +1330,32 @@ public class Notification implements Parcelable public static final String EXTRA_MEDIA_SESSION = "android.mediaSession"; /** + * {@link #extras} key: A {@code CharSequence} name of a remote device used for a media session + * associated with a {@link Notification.MediaStyle} notification. This will show in the media + * controls output switcher instead of the local device name. + * @hide + */ + @TestApi + public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice"; + + /** + * {@link #extras} key: A {@code int} resource ID for an icon that should show in the output + * switcher of the media controls for a {@link Notification.MediaStyle} notification. + * @hide + */ + @TestApi + public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon"; + + /** + * {@link #extras} key: A {@code PendingIntent} that will replace the default action for the + * media controls output switcher chip, associated with a {@link Notification.MediaStyle} + * notification. This should launch an activity. + * @hide + */ + @TestApi + public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent"; + + /** * {@link #extras} key: the indices of actions to be shown in the compact view, * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}. */ @@ -8943,6 +8969,9 @@ public class Notification implements Parcelable private int[] mActionsToShowInCompact = null; private MediaSession.Token mToken; + private CharSequence mDeviceName; + private int mDeviceIcon; + private PendingIntent mDeviceIntent; public MediaStyle() { } @@ -8976,6 +9005,32 @@ public class Notification implements Parcelable } /** + * For media notifications associated with playback on a remote device, provide device + * information that will replace the default values for the output switcher chip on the + * media control, as well as an intent to use when the output switcher chip is tapped, + * on devices where this is supported. + * + * @param deviceName The name of the remote device to display + * @param iconResource Icon resource representing the device + * @param chipIntent PendingIntent to send when the output switcher is tapped. May be + * {@code null}, in which case the output switcher will be disabled. + * This intent should open an Activity or it will be ignored. + * @return MediaStyle + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) + @NonNull + public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName, + @DrawableRes int iconResource, @Nullable PendingIntent chipIntent) { + mDeviceName = deviceName; + mDeviceIcon = iconResource; + mDeviceIntent = chipIntent; + return this; + } + + /** * @hide */ @Override @@ -9023,6 +9078,15 @@ public class Notification implements Parcelable if (mActionsToShowInCompact != null) { extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact); } + if (mDeviceName != null) { + extras.putCharSequence(EXTRA_MEDIA_REMOTE_DEVICE, mDeviceName); + } + if (mDeviceIcon > 0) { + extras.putInt(EXTRA_MEDIA_REMOTE_ICON, mDeviceIcon); + } + if (mDeviceIntent != null) { + extras.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, mDeviceIntent); + } } /** @@ -9038,6 +9102,15 @@ public class Notification implements Parcelable if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) { mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS); } + if (extras.containsKey(EXTRA_MEDIA_REMOTE_DEVICE)) { + mDeviceName = extras.getCharSequence(EXTRA_MEDIA_REMOTE_DEVICE); + } + if (extras.containsKey(EXTRA_MEDIA_REMOTE_ICON)) { + mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON); + } + if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) { + mDeviceIntent = extras.getParcelable(EXTRA_MEDIA_REMOTE_INTENT); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index b3e66829a72b..ce7a6977df92 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -371,13 +371,6 @@ public class MediaControlPanel { // Output switcher chip ViewGroup seamlessView = mMediaViewHolder.getSeamless(); seamlessView.setVisibility(View.VISIBLE); - seamlessView.setOnClickListener( - v -> { - if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - mMediaOutputDialogFactory.create(data.getPackageName(), true, - mMediaViewHolder.getSeamlessButton()); - } - }); ImageView iconView = mMediaViewHolder.getSeamlessIcon(); TextView deviceName = mMediaViewHolder.getSeamlessText(); final MediaDeviceData device = data.getDevice(); @@ -387,8 +380,8 @@ public class MediaControlPanel { final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f; mMediaViewHolder.getSeamlessButton().setAlpha(seamlessAlpha); seamlessView.setEnabled(!seamlessDisabled); - String deviceString = null; - if (device != null && device.getEnabled()) { + CharSequence deviceString = mContext.getString(R.string.media_seamless_other_device); + if (device != null) { Drawable icon = device.getIcon(); if (icon instanceof AdaptiveIcon) { AdaptiveIcon aIcon = (AdaptiveIcon) icon; @@ -399,13 +392,32 @@ public class MediaControlPanel { } deviceString = device.getName(); } else { - // Reset to default - Log.w(TAG, "Device is null or not enabled: " + device + ", not binding output chip."); + // Set to default icon iconView.setImageResource(R.drawable.ic_media_home_devices); - deviceString = mContext.getString(R.string.media_seamless_other_device); } deviceName.setText(deviceString); seamlessView.setContentDescription(deviceString); + seamlessView.setOnClickListener( + v -> { + if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + return; + } + if (device.getIntent() != null) { + if (device.getIntent().isActivity()) { + mActivityStarter.startActivity( + device.getIntent().getIntent(), true); + } else { + try { + device.getIntent().send(); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Device pending intent was canceled"); + } + } + } else { + mMediaOutputDialogFactory.create(data.getPackageName(), true, + mMediaViewHolder.getSeamlessButton()); + } + }); // Dismiss mMediaViewHolder.getDismissText().setAlpha(isDismissible ? 1 : DISABLED_ALPHA); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index 4b8dfdee040e..500e82efdb0a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -164,8 +164,17 @@ data class MediaAction( ) /** State of the media device. */ -data class MediaDeviceData( +data class MediaDeviceData +@JvmOverloads constructor( + /** Whether or not to enable the chip */ val enabled: Boolean, + + /** Device icon to show in the chip */ val icon: Drawable?, - val name: String? + + /** Device display name */ + val name: CharSequence?, + + /** Optional intent to override the default output switcher for this control */ + val intent: PendingIntent? = null ) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 240ca3678765..e1ff110f8d73 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -27,8 +27,6 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.ImageDecoder import android.graphics.drawable.Icon @@ -129,7 +127,7 @@ class MediaDataManager( private val useQsMediaPlayer: Boolean, private val systemClock: SystemClock, private val tunerService: TunerService, - private val mediaFlags: MediaFlags, + private val mediaFlags: MediaFlags ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener { companion object { @@ -170,20 +168,9 @@ class MediaDataManager( /** * Check whether this notification is an RCN - * TODO(b/204910409) implement new API for explicitly declaring this */ private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean { - val pm = context.packageManager - try { - val info = pm.getApplicationInfo(sbn.packageName, PackageManager.MATCH_SYSTEM_ONLY) - if (info.privateFlags and ApplicationInfo.PRIVATE_FLAG_PRIVILEGED != 0) { - val extras = sbn.notification.extras - if (extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) { - return true - } - } - } catch (e: PackageManager.NameNotFoundException) { } - return false + return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE) } @Inject @@ -597,6 +584,25 @@ class MediaDataManager( artist = HybridGroupManager.resolveText(notif) } + // Device name (used for remote cast notifications) + var device: MediaDeviceData? = null + if (isRemoteCastNotification(sbn)) { + val extras = sbn.notification.extras + val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null) + val deviceIcon = extras.getInt(Notification.EXTRA_MEDIA_REMOTE_ICON, -1) + val deviceIntent = extras.getParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT) + as PendingIntent? + Log.d(TAG, "$key is RCN for $deviceName") + + if (deviceName != null && deviceIcon > -1) { + // Name and icon must be present, but intent may be null + val enabled = deviceIntent != null && deviceIntent.isActivity + val deviceDrawable = Icon.createWithResource(sbn.packageName, deviceIcon) + .loadDrawable(sbn.getPackageContext(context)) + device = MediaDeviceData(enabled, deviceDrawable, deviceName, deviceIntent) + } + } + // Control buttons // If flag is enabled and controller has a PlaybackState, create actions from session info // Otherwise, use the notification actions @@ -624,7 +630,7 @@ class MediaDataManager( val active = mediaEntries[key]?.active ?: true onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app, smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed, - semanticActions, sbn.packageName, token, notif.contentIntent, null, + semanticActions, sbn.packageName, token, notif.contentIntent, device, active, resumeAction = resumeAction, playbackLocation = playbackLocation, notificationKey = key, hasCheckedForResume = hasCheckedForResume, isPlaying = isPlaying, isClearable = sbn.isClearable(), diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt index bed254fe8249..934b01c7e888 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt @@ -77,6 +77,12 @@ class MediaDeviceManager @Inject constructor( var entry = entries[key] if (entry == null || entry?.token != data.token) { entry?.stop() + if (data.device != null) { + // If we were already provided device info (e.g. from RCN), keep that and don't + // listen for updates, but process once to push updates to listeners + processDevice(key, oldKey, data.device) + return + } val controller = data.token?.let { controllerFactory.create(it) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 609291a983ce..708fc915410c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -74,6 +74,7 @@ private const val SESSION_KEY = "SESSION_KEY" private const val SESSION_ARTIST = "SESSION_ARTIST" private const val SESSION_TITLE = "SESSION_TITLE" private const val USER_ID = 0 +private const val DISABLED_DEVICE_NAME = "DISABLED_DEVICE_NAME" @SmallTest @RunWith(AndroidTestingRunner::class) @@ -131,7 +132,7 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var session: MediaSession private val device = MediaDeviceData(true, null, DEVICE_NAME) - private val disabledDevice = MediaDeviceData(false, null, "Disabled Device") + private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME) private lateinit var mediaData: MediaData private val clock = FakeSystemClock() @@ -396,13 +397,12 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bindDisabledDevice() { seamless.id = 1 - val fallbackString = context.getString(R.string.media_seamless_other_device) player.attachPlayer(holder, MediaViewController.TYPE.PLAYER) val state = mediaData.copy(device = disabledDevice) player.bindPlayer(state, PACKAGE) assertThat(seamless.isEnabled()).isFalse() - assertThat(seamlessText.getText()).isEqualTo(fallbackString) - assertThat(seamless.contentDescription).isEqualTo(fallbackString) + assertThat(seamlessText.getText()).isEqualTo(DISABLED_DEVICE_NAME) + assertThat(seamless.contentDescription).isEqualTo(DISABLED_DEVICE_NAME) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 649ee872c99e..f4fa921703aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -1,6 +1,5 @@ package com.android.systemui.media -import android.app.Notification import android.app.Notification.MediaStyle import android.app.PendingIntent import android.app.smartspace.SmartspaceAction @@ -240,15 +239,14 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testOnNotificationAdded_isRcn_markedRemote() { - val bundle = Bundle().apply { - putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, "Remote Cast Notification") - } val rcn = SbnBuilder().run { setPkg("com.android.systemui") // System package modifyNotification(context).also { it.setSmallIcon(android.R.drawable.ic_media_pause) - it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) - it.addExtras(bundle) + it.setStyle(MediaStyle().apply { + setMediaSession(session.sessionToken) + setRemotePlaybackInfo("Remote device", 0, null) + }) } build() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt index 3d59497fd978..ccf5ab753ec9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt @@ -376,6 +376,24 @@ public class MediaDeviceManagerTest : SysuiTestCase() { verify(mr2, never()).getRoutingSessionForMediaController(eq(controller)) } + @Test + fun testRemotePlaybackDeviceOverride() { + whenever(route.name).thenReturn(DEVICE_NAME) + val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null) + val mediaDataWithDevice = mediaData.copy(device = deviceData) + + // GIVEN media data that already has a device set + manager.onMediaDataLoaded(KEY, null, mediaDataWithDevice) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + + // THEN we keep the device info, and don't register a listener + val data = captureDeviceData(KEY) + assertThat(data.enabled).isFalse() + assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) + verify(lmm, never()).registerCallback(any()) + } + fun captureCallback(): LocalMediaManager.DeviceCallback { val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java) verify(lmm).registerCallback(captor.capture()) diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index bc38087ebc73..9b7070baa6b6 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -6503,7 +6503,7 @@ public class NotificationManagerService extends SystemService { @VisibleForTesting protected void fixNotification(Notification notification, String pkg, String tag, int id, - int userId) throws NameNotFoundException { + int userId) throws NameNotFoundException, RemoteException { final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser( pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId); @@ -6537,6 +6537,21 @@ public class NotificationManagerService extends SystemService { actions.toArray(notification.actions); } + // Ensure MediaStyle has correct permissions for remote device extras + if (notification.isStyle(Notification.MediaStyle.class)) { + int hasMediaContentControlPermission = mPackageManager.checkPermission( + android.Manifest.permission.MEDIA_CONTENT_CONTROL, pkg, userId); + if (hasMediaContentControlPermission != PERMISSION_GRANTED) { + notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_DEVICE); + notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_ICON); + notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_INTENT); + if (DBG) { + Slog.w(TAG, "Package " + pkg + ": Use of setRemotePlayback requires the " + + "MEDIA_CONTENT_CONTROL permission"); + } + } + } + // Remote views? Are they too big? checkRemoteViews(pkg, tag, id, notification); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 9f92294135c0..976b5e178be1 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -102,6 +102,7 @@ import static org.mockito.Mockito.when; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; @@ -241,6 +242,7 @@ import java.util.function.Consumer; @SmallTest @RunWith(AndroidTestingRunner.class) +@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service. @RunWithLooper public class NotificationManagerServiceTest extends UiServiceTestCase { private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; @@ -4003,6 +4005,63 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testMediaStyleRemote_hasPermission() throws RemoteException { + String deviceName = "device"; + when(mPackageManager.checkPermission( + eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt())) + .thenReturn(PERMISSION_GRANTED); + Notification.MediaStyle style = new Notification.MediaStyle(); + style.setRemotePlaybackInfo(deviceName, 0, null); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setStyle(style); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "testMediaStyleRemoteHasPermission", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + NotificationRecord posted = mService.findNotificationLocked( + PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); + Bundle extras = posted.getNotification().extras; + + assertTrue(extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)); + assertEquals(deviceName, extras.getString(Notification.EXTRA_MEDIA_REMOTE_DEVICE)); + } + + @Test + public void testMediaStyleRemote_noPermission() throws RemoteException { + String deviceName = "device"; + when(mPackageManager.checkPermission( + eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt())) + .thenReturn(PERMISSION_DENIED); + Notification.MediaStyle style = new Notification.MediaStyle(); + style.setRemotePlaybackInfo(deviceName, 0, null); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setStyle(style); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "testMediaStyleRemoteNoPermission", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + NotificationRecord posted = mService.findNotificationLocked( + PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); + + assertFalse(posted.getNotification().extras + .containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)); + } + + @Test public void testGetNotificationCountLocked() { String sampleTagToExclude = null; int sampleIdToExclude = 0; |