[DO NOT MERGE] Add placeholder when media control title is blank

When an app posts a media control with no available title, show a
placeholder string with the app name instead

Bug: 274775190
Test: atest MediaDataManagerTest
Change-Id: Ie406c180af48653595e8e222a15b4dda27de2e0e
This commit is contained in:
Beth Thibodeau
2023-05-30 18:45:47 -05:00
parent b43c742dd7
commit 965e514614
3 changed files with 121 additions and 11 deletions

View File

@@ -2382,6 +2382,8 @@
<string name="controls_media_smartspace_rec_item_no_artist_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%2$s</xliff:g></string> <string name="controls_media_smartspace_rec_item_no_artist_description">Play <xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> from <xliff:g id="app_label" example="Spotify">%2$s</xliff:g></string>
<!-- Header title for Smartspace recommendation card within media controls. [CHAR_LIMIT=30] --> <!-- Header title for Smartspace recommendation card within media controls. [CHAR_LIMIT=30] -->
<string name="controls_media_smartspace_rec_header">For You</string> <string name="controls_media_smartspace_rec_header">For You</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>
<!--- ****** Media tap-to-transfer ****** --> <!--- ****** Media tap-to-transfer ****** -->
<!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] --> <!-- Text for a button to undo the media transfer. [CHAR LIMIT=20] -->

View File

@@ -786,12 +786,16 @@ class MediaDataManager(
// Song name // Song name
var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE) var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
if (song == null) { if (song.isNullOrBlank()) {
song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE) song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
} }
if (song == null) { if (song.isNullOrBlank()) {
song = HybridGroupManager.resolveTitle(notif) song = HybridGroupManager.resolveTitle(notif)
} }
if (song.isNullOrBlank()) {
// For apps that don't include a title, add a placeholder
song = context.getString(R.string.controls_media_empty_title, appName)
}
// Explicit Indicator // Explicit Indicator
var isExplicit = false var isExplicit = false

View File

@@ -25,6 +25,7 @@ import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceTarget import android.app.smartspace.SmartspaceTarget
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.media.MediaDescription import android.media.MediaDescription
@@ -76,6 +77,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor import org.mockito.Captor
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.never import org.mockito.Mockito.never
import org.mockito.Mockito.reset import org.mockito.Mockito.reset
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
@@ -516,6 +518,107 @@ class MediaDataManagerTest : SysuiTestCase() {
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
} }
@Test
fun testOnNotificationAdded_emptyTitle_hasPlaceholder() {
// When the manager has a notification with an empty title
val mockPackageManager = mock(PackageManager::class.java)
context.setMockPackageManager(mockPackageManager)
whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
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_hasPlaceholder() {
// GIVEN that the manager has a notification with a blank title
val mockPackageManager = mock(PackageManager::class.java)
context.setMockPackageManager(mockPackageManager)
whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
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 testOnNotificationAdded_emptyMetadata_usesNotificationTitle() {
// When the app sets the metadata title fields to empty strings, but does include a
// non-blank notification title
val mockPackageManager = mock(PackageManager::class.java)
context.setMockPackageManager(mockPackageManager)
whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
whenever(controller.metadata)
.thenReturn(
metadataBuilder
.putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, SESSION_EMPTY_TITLE)
.build()
)
mediaNotification =
SbnBuilder().run {
setPkg(PACKAGE_NAME)
modifyNotification(context).also {
it.setSmallIcon(android.R.drawable.ic_media_pause)
it.setContentTitle(SESSION_TITLE)
it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
}
build()
}
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
// Then the media control is added using the notification's title
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)
)
assertThat(mediaDataCaptor.value.song).isEqualTo(SESSION_TITLE)
}
@Test @Test
fun testOnNotificationRemoved_emptyTitle_notConverted() { fun testOnNotificationRemoved_emptyTitle_notConverted() {
// GIVEN that the manager has a notification with a resume action and empty title. // GIVEN that the manager has a notification with a resume action and empty title.
@@ -529,8 +632,11 @@ class MediaDataManagerTest : SysuiTestCase() {
val data = mediaDataCaptor.value val data = mediaDataCaptor.value
val instanceId = data.instanceId val instanceId = data.instanceId
assertThat(data.resumption).isFalse() 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 // WHEN the notification is removed
reset(listener) reset(listener)
mediaDataManager.onNotificationRemoved(KEY) mediaDataManager.onNotificationRemoved(KEY)
@@ -554,17 +660,15 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test @Test
fun testOnNotificationRemoved_blankTitle_notConverted() { fun testOnNotificationRemoved_blankTitle_notConverted() {
// GIVEN that the manager has a notification with a resume action and blank title. // 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() addNotificationAndLoad()
val data = mediaDataCaptor.value val data = mediaDataCaptor.value
val instanceId = data.instanceId val instanceId = data.instanceId
assertThat(data.resumption).isFalse() 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 // WHEN the notification is removed
reset(listener) reset(listener)