Loading packages/SystemUI/res/values/strings.xml +2 −0 Original line number Diff line number Diff line Loading @@ -2797,6 +2797,8 @@ <string name="controls_media_resume">Resume</string> <!-- Label for button to go to media control settings screen [CHAR_LIMIT=30] --> <string name="controls_media_settings_button">Settings</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> <!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] --> <string name="controls_error_timeout">Inactive, check app</string> Loading packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +6 −2 Original line number Diff line number Diff line Loading @@ -364,12 +364,16 @@ class MediaDataManager( // Song name var song: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE) if (song == null) { if (song.isNullOrBlank()) { song = metadata.getString(MediaMetadata.METADATA_KEY_TITLE) } if (song == null) { if (song.isNullOrBlank()) { 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, app) } // Artist name var artist: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST) Loading packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +97 −1 Original line number Diff line number Diff line Loading @@ -10,11 +10,13 @@ import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat Loading @@ -23,6 +25,8 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock Loading @@ -33,9 +37,11 @@ import org.mockito.Mockito.`when` as whenever private const val KEY = "KEY" private const val KEY_2 = "KEY_2" private const val PACKAGE_NAME = "com.android.systemui" private const val APP_NAME = "SystemUI" private const val APP_NAME = "com.android.systemui.tests" private const val SESSION_ARTIST = "artist" private const val SESSION_TITLE = "title" private const val SESSION_BLANK_TITLE = " " private const val SESSION_EMPTY_TITLE = "" private const val USER_ID = 0 private fun <T> anyObject(): T { Loading @@ -61,6 +67,7 @@ class MediaDataManagerTest : SysuiTestCase() { @JvmField @Rule val mockito = MockitoJUnit.rule() lateinit var mediaDataManager: MediaDataManager lateinit var mediaNotification: StatusBarNotification @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData> @Before fun setup() { Loading Loading @@ -143,6 +150,95 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataRemoved(eq(KEY)) } @Test fun testOnNotificationAdded_emptyTitle_hasPlaceholder() { // When the manager has a notification with an empty title val listener = mock(MediaDataManager.Listener::class.java) mediaDataManager.addListener(listener) 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) ) 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 listener = mock(MediaDataManager.Listener::class.java) mediaDataManager.addListener(listener) 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) ) 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 listener = mock(MediaDataManager.Listener::class.java) mediaDataManager.addListener(listener) 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) ) assertThat(mediaDataCaptor.value.song).isEqualTo(SESSION_TITLE) } @Test fun testOnNotificationRemoved_withResumption() { // GIVEN that the manager has a notification with a resume action Loading Loading
packages/SystemUI/res/values/strings.xml +2 −0 Original line number Diff line number Diff line Loading @@ -2797,6 +2797,8 @@ <string name="controls_media_resume">Resume</string> <!-- Label for button to go to media control settings screen [CHAR_LIMIT=30] --> <string name="controls_media_settings_button">Settings</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> <!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] --> <string name="controls_error_timeout">Inactive, check app</string> Loading
packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +6 −2 Original line number Diff line number Diff line Loading @@ -364,12 +364,16 @@ class MediaDataManager( // Song name var song: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE) if (song == null) { if (song.isNullOrBlank()) { song = metadata.getString(MediaMetadata.METADATA_KEY_TITLE) } if (song == null) { if (song.isNullOrBlank()) { 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, app) } // Artist name var artist: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST) Loading
packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +97 −1 Original line number Diff line number Diff line Loading @@ -10,11 +10,13 @@ import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat Loading @@ -23,6 +25,8 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock Loading @@ -33,9 +37,11 @@ import org.mockito.Mockito.`when` as whenever private const val KEY = "KEY" private const val KEY_2 = "KEY_2" private const val PACKAGE_NAME = "com.android.systemui" private const val APP_NAME = "SystemUI" private const val APP_NAME = "com.android.systemui.tests" private const val SESSION_ARTIST = "artist" private const val SESSION_TITLE = "title" private const val SESSION_BLANK_TITLE = " " private const val SESSION_EMPTY_TITLE = "" private const val USER_ID = 0 private fun <T> anyObject(): T { Loading @@ -61,6 +67,7 @@ class MediaDataManagerTest : SysuiTestCase() { @JvmField @Rule val mockito = MockitoJUnit.rule() lateinit var mediaDataManager: MediaDataManager lateinit var mediaNotification: StatusBarNotification @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData> @Before fun setup() { Loading Loading @@ -143,6 +150,95 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataRemoved(eq(KEY)) } @Test fun testOnNotificationAdded_emptyTitle_hasPlaceholder() { // When the manager has a notification with an empty title val listener = mock(MediaDataManager.Listener::class.java) mediaDataManager.addListener(listener) 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) ) 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 listener = mock(MediaDataManager.Listener::class.java) mediaDataManager.addListener(listener) 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) ) 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 listener = mock(MediaDataManager.Listener::class.java) mediaDataManager.addListener(listener) 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) ) assertThat(mediaDataCaptor.value.song).isEqualTo(SESSION_TITLE) } @Test fun testOnNotificationRemoved_withResumption() { // GIVEN that the manager has a notification with a resume action Loading