From 070eff919c85fd83501e380a92e30caf082e9ffc Mon Sep 17 00:00:00 2001 From: Beth Thibodeau Date: Tue, 30 May 2023 18:45:47 -0500 Subject: 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 Merged-In: Ie406c180af48653595e8e222a15b4dda27de2e0e Merged-In: I1cb6010f19623bd7d5e5bbb5d829b9e4936894b8 --- packages/SystemUI/res/values/strings.xml | 2 + .../com/android/systemui/media/MediaDataManager.kt | 8 +- .../android/systemui/media/MediaDataManagerTest.kt | 98 +++++++++++++++++++++- 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 @@ Resume Settings + + %1$s is running Inactive, check app 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 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 @Before fun setup() { @@ -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 -- cgit v1.2.3-59-g8ed1b