summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Michael Mikhail <michaelmikhil@google.com> 2023-03-28 19:40:57 +0000
committer Michael Mikhail <michaelmikhil@google.com> 2023-04-12 18:27:57 +0000
commit381f6d61d0cec72fbf5b727324466d9b234e0d22 (patch)
tree111ecfd4cf79b979e7c69d1e3b4a9de501c250d5
parentcc96b1e83e7495cffed6d02218ee154e58fcfb4a (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
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt66
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt140
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)