Merge "Prevent media controls with no title" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
910e30bfe5
@@ -571,6 +571,15 @@ public class StatusBarManager {
|
||||
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
|
||||
private static final long MEDIA_CONTROL_SESSION_ACTIONS = 203800354L;
|
||||
|
||||
/**
|
||||
* Media controls based on {@link android.app.Notification.MediaStyle} notifications will be
|
||||
* required to include a non-empty title, either in the {@link android.media.MediaMetadata} or
|
||||
* notification title.
|
||||
*/
|
||||
@ChangeId
|
||||
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||
private static final long MEDIA_CONTROL_REQUIRES_TITLE = 274775190L;
|
||||
|
||||
@UnsupportedAppUsage
|
||||
private Context mContext;
|
||||
private IStatusBarService mService;
|
||||
@@ -1216,6 +1225,21 @@ public class StatusBarManager {
|
||||
return CompatChanges.isChangeEnabled(MEDIA_CONTROL_SESSION_ACTIONS, packageName, user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package must include a non-empty title for its media controls.
|
||||
*
|
||||
* @param packageName App posting media controls
|
||||
* @param user Current user handle
|
||||
* @return true if the app is required to provide a non-empty title
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
|
||||
android.Manifest.permission.LOG_COMPAT_CHANGE})
|
||||
public static boolean isMediaTitleRequiredForApp(String packageName, UserHandle user) {
|
||||
return CompatChanges.isChangeEnabled(MEDIA_CONTROL_REQUIRES_TITLE, packageName, user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the supplied activity can {@link Activity#startActivityForResult(Intent, int)}
|
||||
* a system activity that captures content on the screen to take a screenshot.
|
||||
|
||||
@@ -2583,7 +2583,7 @@
|
||||
<!-- Title for media controls [CHAR_LIMIT=50] -->
|
||||
<string name="controls_media_title">Media</string>
|
||||
<!-- Explanation for closing controls associated with a specific media session [CHAR_LIMIT=50] -->
|
||||
<string name="controls_media_close_session">Hide this media control for <xliff:g id="app_name" example="YouTube Music">%1$s</xliff:g>?</string>
|
||||
<string name="controls_media_close_session">Hide this media control for <xliff:g id="app_name" example="Foo Music App">%1$s</xliff:g>?</string>
|
||||
<!-- Explanation that controls associated with a specific media session are active [CHAR_LIMIT=50] -->
|
||||
<string name="controls_media_active_session">The current media session cannot be hidden.</string>
|
||||
<!-- Label for a button that will hide media controls [CHAR_LIMIT=30] -->
|
||||
@@ -2596,6 +2596,8 @@
|
||||
<string name="controls_media_playing_item_description"><xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> is playing from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
|
||||
<!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
|
||||
<string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>
|
||||
<!-- Placeholder title to inform user that an app has posted media controls [CHAR_LIMIT=NONE] -->
|
||||
<string name="controls_media_empty_title"><xliff:g id="app_name" example="Foo Music App">%1$s</xliff:g> is running</string>
|
||||
|
||||
<!-- Description for button in media controls. Pressing button starts playback [CHAR_LIMIT=NONE] -->
|
||||
<string name="controls_media_button_play">Play</string>
|
||||
|
||||
@@ -43,6 +43,7 @@ import android.media.session.PlaybackState
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import android.os.Process
|
||||
import android.os.RemoteException
|
||||
import android.os.UserHandle
|
||||
import android.provider.Settings
|
||||
import android.service.notification.StatusBarNotification
|
||||
@@ -52,6 +53,7 @@ import android.util.Log
|
||||
import android.util.Pair as APair
|
||||
import androidx.media.utils.MediaConstants
|
||||
import com.android.internal.logging.InstanceId
|
||||
import com.android.internal.statusbar.IStatusBarService
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.R
|
||||
@@ -137,6 +139,8 @@ internal val EMPTY_SMARTSPACE_MEDIA_DATA =
|
||||
expiryTimeMs = 0,
|
||||
)
|
||||
|
||||
const val MEDIA_TITLE_ERROR_MESSAGE = "Invalid media data: title is null or blank."
|
||||
|
||||
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
|
||||
return sbn.notification.isMediaNotification()
|
||||
}
|
||||
@@ -181,6 +185,7 @@ class MediaDataManager(
|
||||
private val logger: MediaUiEventLogger,
|
||||
private val smartspaceManager: SmartspaceManager,
|
||||
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||
private val statusBarService: IStatusBarService,
|
||||
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
|
||||
|
||||
companion object {
|
||||
@@ -252,6 +257,7 @@ class MediaDataManager(
|
||||
mediaFlags: MediaFlags,
|
||||
logger: MediaUiEventLogger,
|
||||
smartspaceManager: SmartspaceManager,
|
||||
statusBarService: IStatusBarService,
|
||||
keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||
) : this(
|
||||
context,
|
||||
@@ -277,6 +283,7 @@ class MediaDataManager(
|
||||
logger,
|
||||
smartspaceManager,
|
||||
keyguardUpdateMonitor,
|
||||
statusBarService,
|
||||
)
|
||||
|
||||
private val appChangeReceiver =
|
||||
@@ -378,21 +385,21 @@ class MediaDataManager(
|
||||
|
||||
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
|
||||
if (useQsMediaPlayer && isMediaNotification(sbn)) {
|
||||
var logEvent = false
|
||||
var isNewlyActiveEntry = false
|
||||
Assert.isMainThread()
|
||||
val oldKey = findExistingEntry(key, sbn.packageName)
|
||||
if (oldKey == null) {
|
||||
val instanceId = logger.getNewInstanceId()
|
||||
val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId)
|
||||
mediaEntries.put(key, temp)
|
||||
logEvent = true
|
||||
isNewlyActiveEntry = true
|
||||
} else if (oldKey != key) {
|
||||
// Resume -> active conversion; move to new key
|
||||
val oldData = mediaEntries.remove(oldKey)!!
|
||||
logEvent = true
|
||||
isNewlyActiveEntry = true
|
||||
mediaEntries.put(key, oldData)
|
||||
}
|
||||
loadMediaData(key, sbn, oldKey, logEvent)
|
||||
loadMediaData(key, sbn, oldKey, isNewlyActiveEntry)
|
||||
} else {
|
||||
onNotificationRemoved(key)
|
||||
}
|
||||
@@ -475,9 +482,9 @@ class MediaDataManager(
|
||||
key: String,
|
||||
sbn: StatusBarNotification,
|
||||
oldKey: String?,
|
||||
logEvent: Boolean = false
|
||||
isNewlyActiveEntry: Boolean = false,
|
||||
) {
|
||||
backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, logEvent) }
|
||||
backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
|
||||
}
|
||||
|
||||
/** Add a listener for changes in this class */
|
||||
@@ -601,9 +608,11 @@ class MediaDataManager(
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeEntry(key: String) {
|
||||
private fun removeEntry(key: String, logEvent: Boolean = true) {
|
||||
mediaEntries.remove(key)?.let {
|
||||
logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
|
||||
if (logEvent) {
|
||||
logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
|
||||
}
|
||||
}
|
||||
notifyMediaDataRemoved(key)
|
||||
}
|
||||
@@ -751,7 +760,7 @@ class MediaDataManager(
|
||||
key: String,
|
||||
sbn: StatusBarNotification,
|
||||
oldKey: String?,
|
||||
logEvent: Boolean = false
|
||||
isNewlyActiveEntry: Boolean = false,
|
||||
) {
|
||||
val token =
|
||||
sbn.notification.extras.getParcelable(
|
||||
@@ -772,6 +781,42 @@ class MediaDataManager(
|
||||
)
|
||||
?: getAppInfoFromPackage(sbn.packageName)
|
||||
|
||||
// App name
|
||||
val appName = getAppName(sbn, appInfo)
|
||||
|
||||
// Song name
|
||||
var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
|
||||
if (song == null) {
|
||||
song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
|
||||
}
|
||||
if (song == null) {
|
||||
song = HybridGroupManager.resolveTitle(notif)
|
||||
}
|
||||
if (song.isNullOrBlank()) {
|
||||
if (mediaFlags.isMediaTitleRequired(sbn.packageName, sbn.user)) {
|
||||
// App is required to provide a title: cancel the underlying notification
|
||||
try {
|
||||
statusBarService.onNotificationError(
|
||||
sbn.packageName,
|
||||
sbn.tag,
|
||||
sbn.id,
|
||||
sbn.uid,
|
||||
sbn.initialPid,
|
||||
MEDIA_TITLE_ERROR_MESSAGE,
|
||||
sbn.user.identifier
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "cancelNotification failed: $e")
|
||||
}
|
||||
// Only add log for media removed if active media is updated with invalid title.
|
||||
foregroundExecutor.execute { removeEntry(key, !isNewlyActiveEntry) }
|
||||
return
|
||||
} else {
|
||||
// For apps that don't have the title requirement yet, add a placeholder
|
||||
song = context.getString(R.string.controls_media_empty_title, appName)
|
||||
}
|
||||
}
|
||||
|
||||
// Album art
|
||||
var artworkBitmap = metadata?.let { loadBitmapFromUri(it) }
|
||||
if (artworkBitmap == null) {
|
||||
@@ -787,21 +832,9 @@ class MediaDataManager(
|
||||
Icon.createWithBitmap(artworkBitmap)
|
||||
}
|
||||
|
||||
// App name
|
||||
val appName = getAppName(sbn, appInfo)
|
||||
|
||||
// App Icon
|
||||
val smallIcon = sbn.notification.smallIcon
|
||||
|
||||
// Song name
|
||||
var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
|
||||
if (song == null) {
|
||||
song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
|
||||
}
|
||||
if (song == null) {
|
||||
song = HybridGroupManager.resolveTitle(notif)
|
||||
}
|
||||
|
||||
// Explicit Indicator
|
||||
var isExplicit = false
|
||||
if (mediaFlags.isExplicitIndicatorEnabled()) {
|
||||
@@ -873,7 +906,7 @@ class MediaDataManager(
|
||||
val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
|
||||
val appUid = appInfo?.uid ?: Process.INVALID_UID
|
||||
|
||||
if (logEvent) {
|
||||
if (isNewlyActiveEntry) {
|
||||
logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId)
|
||||
logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
|
||||
} else if (playbackLocation != currentEntry?.playbackLocation) {
|
||||
|
||||
@@ -64,4 +64,9 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
|
||||
|
||||
/** Check whether we allow remote media to generate resume controls */
|
||||
fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
|
||||
|
||||
/** Check whether app is required to provide a non-empty media title */
|
||||
fun isMediaTitleRequired(packageName: String, user: UserHandle): Boolean {
|
||||
return StatusBarManager.isMediaTitleRequiredForApp(packageName, user)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.app.smartspace.SmartspaceConfig
|
||||
import android.app.smartspace.SmartspaceManager
|
||||
import android.app.smartspace.SmartspaceTarget
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Icon
|
||||
import android.media.MediaDescription
|
||||
@@ -40,6 +41,7 @@ import android.testing.TestableLooper.RunWithLooper
|
||||
import androidx.media.utils.MediaConstants
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.logging.InstanceId
|
||||
import com.android.internal.statusbar.IStatusBarService
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.systemui.InstanceIdSequenceFake
|
||||
import com.android.systemui.R
|
||||
@@ -76,6 +78,7 @@ import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.verify
|
||||
@@ -130,6 +133,7 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
@Mock lateinit var activityStarter: ActivityStarter
|
||||
@Mock lateinit var smartspaceManager: SmartspaceManager
|
||||
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||
@Mock lateinit var statusBarService: IStatusBarService
|
||||
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
|
||||
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
|
||||
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
|
||||
@@ -192,7 +196,8 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
mediaFlags = mediaFlags,
|
||||
logger = logger,
|
||||
smartspaceManager = smartspaceManager,
|
||||
keyguardUpdateMonitor = keyguardUpdateMonitor
|
||||
keyguardUpdateMonitor = keyguardUpdateMonitor,
|
||||
statusBarService = statusBarService,
|
||||
)
|
||||
verify(tunerService)
|
||||
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
|
||||
@@ -517,19 +522,211 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnNotificationRemoved_emptyTitle_notConverted() {
|
||||
// GIVEN that the manager has a notification with a resume action and empty title.
|
||||
fun testOnNotificationAdded_emptyTitle_isRequired_notLoaded() {
|
||||
// When the manager has a notification with an empty title, and the app is required
|
||||
// to include a non-empty title
|
||||
whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
|
||||
whenever(controller.metadata)
|
||||
.thenReturn(
|
||||
metadataBuilder
|
||||
.putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
|
||||
.build()
|
||||
)
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
|
||||
// Then the media control is not added and we report a notification error
|
||||
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
|
||||
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
|
||||
verify(statusBarService)
|
||||
.onNotificationError(
|
||||
eq(PACKAGE_NAME),
|
||||
eq(mediaNotification.tag),
|
||||
eq(mediaNotification.id),
|
||||
eq(mediaNotification.uid),
|
||||
eq(mediaNotification.initialPid),
|
||||
eq(MEDIA_TITLE_ERROR_MESSAGE),
|
||||
eq(mediaNotification.user.identifier)
|
||||
)
|
||||
verify(listener, never())
|
||||
.onMediaDataLoaded(
|
||||
eq(KEY),
|
||||
eq(null),
|
||||
capture(mediaDataCaptor),
|
||||
eq(true),
|
||||
eq(0),
|
||||
eq(false)
|
||||
)
|
||||
verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
|
||||
verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnNotificationAdded_blankTitle_isRequired_notLoaded() {
|
||||
// When the manager has a notification with a blank title, and the app is required
|
||||
// to include a non-empty title
|
||||
whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
|
||||
whenever(controller.metadata)
|
||||
.thenReturn(
|
||||
metadataBuilder
|
||||
.putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
|
||||
.build()
|
||||
)
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
|
||||
// Then the media control is not added and we report a notification error
|
||||
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
|
||||
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
|
||||
verify(statusBarService)
|
||||
.onNotificationError(
|
||||
eq(PACKAGE_NAME),
|
||||
eq(mediaNotification.tag),
|
||||
eq(mediaNotification.id),
|
||||
eq(mediaNotification.uid),
|
||||
eq(mediaNotification.initialPid),
|
||||
eq(MEDIA_TITLE_ERROR_MESSAGE),
|
||||
eq(mediaNotification.user.identifier)
|
||||
)
|
||||
verify(listener, never())
|
||||
.onMediaDataLoaded(
|
||||
eq(KEY),
|
||||
eq(null),
|
||||
capture(mediaDataCaptor),
|
||||
eq(true),
|
||||
eq(0),
|
||||
eq(false)
|
||||
)
|
||||
verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
|
||||
verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnNotificationUpdated_invalidTitle_isRequired_logMediaRemoved() {
|
||||
// When the app is required to provide a non-blank title, and updates a previously valid
|
||||
// title to an empty one
|
||||
whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
|
||||
addNotificationAndLoad()
|
||||
val data = mediaDataCaptor.value
|
||||
|
||||
verify(listener)
|
||||
.onMediaDataLoaded(
|
||||
eq(KEY),
|
||||
eq(null),
|
||||
capture(mediaDataCaptor),
|
||||
eq(true),
|
||||
eq(0),
|
||||
eq(false)
|
||||
)
|
||||
|
||||
reset(listener)
|
||||
whenever(controller.metadata)
|
||||
.thenReturn(
|
||||
metadataBuilder
|
||||
.putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
|
||||
.build()
|
||||
)
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
|
||||
// Then the media control is removed
|
||||
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
|
||||
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
|
||||
verify(statusBarService)
|
||||
.onNotificationError(
|
||||
eq(PACKAGE_NAME),
|
||||
eq(mediaNotification.tag),
|
||||
eq(mediaNotification.id),
|
||||
eq(mediaNotification.uid),
|
||||
eq(mediaNotification.initialPid),
|
||||
eq(MEDIA_TITLE_ERROR_MESSAGE),
|
||||
eq(mediaNotification.user.identifier)
|
||||
)
|
||||
verify(listener, never())
|
||||
.onMediaDataLoaded(
|
||||
eq(KEY),
|
||||
eq(null),
|
||||
capture(mediaDataCaptor),
|
||||
eq(true),
|
||||
eq(0),
|
||||
eq(false)
|
||||
)
|
||||
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnNotificationAdded_emptyTitle_notRequired_hasPlaceholder() {
|
||||
// When the manager has a notification with an empty title, and the app is not
|
||||
// required to include a non-empty title
|
||||
val mockPackageManager = mock(PackageManager::class.java)
|
||||
context.setMockPackageManager(mockPackageManager)
|
||||
whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
|
||||
whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false)
|
||||
whenever(controller.metadata)
|
||||
.thenReturn(
|
||||
metadataBuilder
|
||||
.putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
|
||||
.build()
|
||||
)
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
|
||||
// Then a media control is created with a placeholder title string
|
||||
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
|
||||
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
|
||||
verify(listener)
|
||||
.onMediaDataLoaded(
|
||||
eq(KEY),
|
||||
eq(null),
|
||||
capture(mediaDataCaptor),
|
||||
eq(true),
|
||||
eq(0),
|
||||
eq(false)
|
||||
)
|
||||
val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
|
||||
assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnNotificationAdded_blankTitle_notRequired_hasPlaceholder() {
|
||||
// GIVEN that the manager has a notification with a blank title, and the app is not
|
||||
// required to include a non-empty title
|
||||
val mockPackageManager = mock(PackageManager::class.java)
|
||||
context.setMockPackageManager(mockPackageManager)
|
||||
whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
|
||||
whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false)
|
||||
whenever(controller.metadata)
|
||||
.thenReturn(
|
||||
metadataBuilder
|
||||
.putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
|
||||
.build()
|
||||
)
|
||||
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
|
||||
|
||||
// Then a media control is created with a placeholder title string
|
||||
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
|
||||
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
|
||||
verify(listener)
|
||||
.onMediaDataLoaded(
|
||||
eq(KEY),
|
||||
eq(null),
|
||||
capture(mediaDataCaptor),
|
||||
eq(true),
|
||||
eq(0),
|
||||
eq(false)
|
||||
)
|
||||
val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
|
||||
assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnNotificationRemoved_emptyTitle_notConverted() {
|
||||
// GIVEN that the manager has a notification with a resume action and empty title.
|
||||
addNotificationAndLoad()
|
||||
val data = mediaDataCaptor.value
|
||||
val instanceId = data.instanceId
|
||||
assertThat(data.resumption).isFalse()
|
||||
mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
|
||||
mediaDataManager.onMediaDataLoaded(
|
||||
KEY,
|
||||
null,
|
||||
data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {})
|
||||
)
|
||||
|
||||
// WHEN the notification is removed
|
||||
reset(listener)
|
||||
@@ -554,17 +751,15 @@ class MediaDataManagerTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun testOnNotificationRemoved_blankTitle_notConverted() {
|
||||
// GIVEN that the manager has a notification with a resume action and blank title.
|
||||
whenever(controller.metadata)
|
||||
.thenReturn(
|
||||
metadataBuilder
|
||||
.putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
|
||||
.build()
|
||||
)
|
||||
addNotificationAndLoad()
|
||||
val data = mediaDataCaptor.value
|
||||
val instanceId = data.instanceId
|
||||
assertThat(data.resumption).isFalse()
|
||||
mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
|
||||
mediaDataManager.onMediaDataLoaded(
|
||||
KEY,
|
||||
null,
|
||||
data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {})
|
||||
)
|
||||
|
||||
// WHEN the notification is removed
|
||||
reset(listener)
|
||||
|
||||
Reference in New Issue
Block a user