diff options
| author | 2023-03-28 19:40:57 +0000 | |
|---|---|---|
| committer | 2023-04-12 18:27:57 +0000 | |
| commit | 381f6d61d0cec72fbf5b727324466d9b234e0d22 (patch) | |
| tree | 111ecfd4cf79b979e7c69d1e3b4a9de501c250d5 | |
| parent | cc96b1e83e7495cffed6d02218ee154e58fcfb4a (diff) | |
Cancel notification if media title is invalid
Requires notifications with media style not to have empty titles. If
media session contains empty title, it cancels the notification and
sends a message that the title is null or blank.
Bug: 274775190
Test: atest MediaDataManagerTest.
Change-Id: I54c4187f57bf09cd992bca99503041a53b1a7259
2 files changed, 176 insertions, 30 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 4cc041074a8d..b09f201e237b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -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 @@ -51,6 +52,7 @@ import android.text.TextUtils import android.util.Log 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 @@ -136,6 +138,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() } @@ -180,6 +184,7 @@ class MediaDataManager( private val logger: MediaUiEventLogger, private val smartspaceManager: SmartspaceManager, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val statusBarService: IStatusBarService, ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener { companion object { @@ -244,6 +249,7 @@ class MediaDataManager( mediaFlags: MediaFlags, logger: MediaUiEventLogger, smartspaceManager: SmartspaceManager, + statusBarService: IStatusBarService, keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : this( context, @@ -269,6 +275,7 @@ class MediaDataManager( logger, smartspaceManager, keyguardUpdateMonitor, + statusBarService, ) private val appChangeReceiver = @@ -370,21 +377,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) } @@ -467,9 +474,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 */ @@ -593,9 +600,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) } @@ -743,7 +752,7 @@ class MediaDataManager( key: String, sbn: StatusBarNotification, oldKey: String?, - logEvent: Boolean = false + isNewlyActiveEntry: Boolean = false, ) { val token = sbn.notification.extras.getParcelable( @@ -757,6 +766,34 @@ class MediaDataManager( val metadata = mediaController.metadata val notif: Notification = sbn.notification + // 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) + } + // Media data must have a title. + if (song.isNullOrBlank()) { + 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 + } + val appInfo = notif.extras.getParcelable( Notification.EXTRA_BUILDER_APPLICATION_INFO, @@ -785,15 +822,6 @@ class MediaDataManager( // 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()) { @@ -865,7 +893,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) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index d428db7b9dda..0a1db60e075c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -40,6 +40,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 @@ -130,6 +131,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 +194,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 +520,136 @@ 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_notLoaded() { + // GIVEN that the manager has a notification with an empty title. whenever(controller.metadata) .thenReturn( metadataBuilder .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE) .build() ) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + 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_notLoaded() { + // GIVEN that the manager has a notification with a blank title. + whenever(controller.metadata) + .thenReturn( + metadataBuilder + .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE) + .build() + ) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + 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_logMediaRemoved() { + 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) + 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 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 +674,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) |