diff options
3 files changed, 105 insertions, 3 deletions
| diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index db45a60ab7c0..13f6d3cacbc3 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -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> diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 299ae5b50aa9..a09adfb90d9f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -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) 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 59c2d0e86c56..739c00e3ed30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -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 @@ -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 @@ -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 { @@ -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() { @@ -144,6 +151,95 @@ class MediaDataManagerTest : SysuiTestCase() {      }      @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          val listener = TestListener() |