Merge "Cancel notification if media title is invalid" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
321f474e52
@@ -43,6 +43,7 @@ import android.media.session.PlaybackState
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
|
import android.os.RemoteException
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.service.notification.StatusBarNotification
|
import android.service.notification.StatusBarNotification
|
||||||
@@ -52,6 +53,7 @@ import android.util.Log
|
|||||||
import android.util.Pair as APair
|
import android.util.Pair as APair
|
||||||
import androidx.media.utils.MediaConstants
|
import androidx.media.utils.MediaConstants
|
||||||
import com.android.internal.logging.InstanceId
|
import com.android.internal.logging.InstanceId
|
||||||
|
import com.android.internal.statusbar.IStatusBarService
|
||||||
import com.android.keyguard.KeyguardUpdateMonitor
|
import com.android.keyguard.KeyguardUpdateMonitor
|
||||||
import com.android.systemui.Dumpable
|
import com.android.systemui.Dumpable
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
@@ -137,6 +139,8 @@ internal val EMPTY_SMARTSPACE_MEDIA_DATA =
|
|||||||
expiryTimeMs = 0,
|
expiryTimeMs = 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const val MEDIA_TITLE_ERROR_MESSAGE = "Invalid media data: title is null or blank."
|
||||||
|
|
||||||
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
|
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
|
||||||
return sbn.notification.isMediaNotification()
|
return sbn.notification.isMediaNotification()
|
||||||
}
|
}
|
||||||
@@ -181,6 +185,7 @@ class MediaDataManager(
|
|||||||
private val logger: MediaUiEventLogger,
|
private val logger: MediaUiEventLogger,
|
||||||
private val smartspaceManager: SmartspaceManager,
|
private val smartspaceManager: SmartspaceManager,
|
||||||
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||||
|
private val statusBarService: IStatusBarService,
|
||||||
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
|
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -252,6 +257,7 @@ class MediaDataManager(
|
|||||||
mediaFlags: MediaFlags,
|
mediaFlags: MediaFlags,
|
||||||
logger: MediaUiEventLogger,
|
logger: MediaUiEventLogger,
|
||||||
smartspaceManager: SmartspaceManager,
|
smartspaceManager: SmartspaceManager,
|
||||||
|
statusBarService: IStatusBarService,
|
||||||
keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||||
) : this(
|
) : this(
|
||||||
context,
|
context,
|
||||||
@@ -277,6 +283,7 @@ class MediaDataManager(
|
|||||||
logger,
|
logger,
|
||||||
smartspaceManager,
|
smartspaceManager,
|
||||||
keyguardUpdateMonitor,
|
keyguardUpdateMonitor,
|
||||||
|
statusBarService,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val appChangeReceiver =
|
private val appChangeReceiver =
|
||||||
@@ -378,21 +385,21 @@ class MediaDataManager(
|
|||||||
|
|
||||||
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
|
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
|
||||||
if (useQsMediaPlayer && isMediaNotification(sbn)) {
|
if (useQsMediaPlayer && isMediaNotification(sbn)) {
|
||||||
var logEvent = false
|
var isNewlyActiveEntry = false
|
||||||
Assert.isMainThread()
|
Assert.isMainThread()
|
||||||
val oldKey = findExistingEntry(key, sbn.packageName)
|
val oldKey = findExistingEntry(key, sbn.packageName)
|
||||||
if (oldKey == null) {
|
if (oldKey == null) {
|
||||||
val instanceId = logger.getNewInstanceId()
|
val instanceId = logger.getNewInstanceId()
|
||||||
val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId)
|
val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId)
|
||||||
mediaEntries.put(key, temp)
|
mediaEntries.put(key, temp)
|
||||||
logEvent = true
|
isNewlyActiveEntry = true
|
||||||
} else if (oldKey != key) {
|
} else if (oldKey != key) {
|
||||||
// Resume -> active conversion; move to new key
|
// Resume -> active conversion; move to new key
|
||||||
val oldData = mediaEntries.remove(oldKey)!!
|
val oldData = mediaEntries.remove(oldKey)!!
|
||||||
logEvent = true
|
isNewlyActiveEntry = true
|
||||||
mediaEntries.put(key, oldData)
|
mediaEntries.put(key, oldData)
|
||||||
}
|
}
|
||||||
loadMediaData(key, sbn, oldKey, logEvent)
|
loadMediaData(key, sbn, oldKey, isNewlyActiveEntry)
|
||||||
} else {
|
} else {
|
||||||
onNotificationRemoved(key)
|
onNotificationRemoved(key)
|
||||||
}
|
}
|
||||||
@@ -475,9 +482,9 @@ class MediaDataManager(
|
|||||||
key: String,
|
key: String,
|
||||||
sbn: StatusBarNotification,
|
sbn: StatusBarNotification,
|
||||||
oldKey: String?,
|
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 */
|
/** Add a listener for changes in this class */
|
||||||
@@ -601,9 +608,11 @@ class MediaDataManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeEntry(key: String) {
|
private fun removeEntry(key: String, logEvent: Boolean = true) {
|
||||||
mediaEntries.remove(key)?.let {
|
mediaEntries.remove(key)?.let {
|
||||||
logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
|
if (logEvent) {
|
||||||
|
logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
notifyMediaDataRemoved(key)
|
notifyMediaDataRemoved(key)
|
||||||
}
|
}
|
||||||
@@ -751,7 +760,7 @@ class MediaDataManager(
|
|||||||
key: String,
|
key: String,
|
||||||
sbn: StatusBarNotification,
|
sbn: StatusBarNotification,
|
||||||
oldKey: String?,
|
oldKey: String?,
|
||||||
logEvent: Boolean = false
|
isNewlyActiveEntry: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val token =
|
val token =
|
||||||
sbn.notification.extras.getParcelable(
|
sbn.notification.extras.getParcelable(
|
||||||
@@ -765,6 +774,34 @@ class MediaDataManager(
|
|||||||
val metadata = mediaController.metadata
|
val metadata = mediaController.metadata
|
||||||
val notif: Notification = sbn.notification
|
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 =
|
val appInfo =
|
||||||
notif.extras.getParcelable(
|
notif.extras.getParcelable(
|
||||||
Notification.EXTRA_BUILDER_APPLICATION_INFO,
|
Notification.EXTRA_BUILDER_APPLICATION_INFO,
|
||||||
@@ -793,15 +830,6 @@ class MediaDataManager(
|
|||||||
// App Icon
|
// App Icon
|
||||||
val smallIcon = sbn.notification.smallIcon
|
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
|
// Explicit Indicator
|
||||||
var isExplicit = false
|
var isExplicit = false
|
||||||
if (mediaFlags.isExplicitIndicatorEnabled()) {
|
if (mediaFlags.isExplicitIndicatorEnabled()) {
|
||||||
@@ -873,7 +901,7 @@ class MediaDataManager(
|
|||||||
val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
|
val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
|
||||||
val appUid = appInfo?.uid ?: Process.INVALID_UID
|
val appUid = appInfo?.uid ?: Process.INVALID_UID
|
||||||
|
|
||||||
if (logEvent) {
|
if (isNewlyActiveEntry) {
|
||||||
logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId)
|
logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId)
|
||||||
logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
|
logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
|
||||||
} else if (playbackLocation != currentEntry?.playbackLocation) {
|
} else if (playbackLocation != currentEntry?.playbackLocation) {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import android.testing.TestableLooper.RunWithLooper
|
|||||||
import androidx.media.utils.MediaConstants
|
import androidx.media.utils.MediaConstants
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.internal.logging.InstanceId
|
import com.android.internal.logging.InstanceId
|
||||||
|
import com.android.internal.statusbar.IStatusBarService
|
||||||
import com.android.keyguard.KeyguardUpdateMonitor
|
import com.android.keyguard.KeyguardUpdateMonitor
|
||||||
import com.android.systemui.InstanceIdSequenceFake
|
import com.android.systemui.InstanceIdSequenceFake
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
@@ -130,6 +131,7 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
@Mock lateinit var activityStarter: ActivityStarter
|
@Mock lateinit var activityStarter: ActivityStarter
|
||||||
@Mock lateinit var smartspaceManager: SmartspaceManager
|
@Mock lateinit var smartspaceManager: SmartspaceManager
|
||||||
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||||
|
@Mock lateinit var statusBarService: IStatusBarService
|
||||||
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
|
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
|
||||||
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
|
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
|
||||||
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
|
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
|
||||||
@@ -192,7 +194,8 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
mediaFlags = mediaFlags,
|
mediaFlags = mediaFlags,
|
||||||
logger = logger,
|
logger = logger,
|
||||||
smartspaceManager = smartspaceManager,
|
smartspaceManager = smartspaceManager,
|
||||||
keyguardUpdateMonitor = keyguardUpdateMonitor
|
keyguardUpdateMonitor = keyguardUpdateMonitor,
|
||||||
|
statusBarService = statusBarService,
|
||||||
)
|
)
|
||||||
verify(tunerService)
|
verify(tunerService)
|
||||||
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
|
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
|
||||||
@@ -517,19 +520,136 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testOnNotificationRemoved_emptyTitle_notConverted() {
|
fun testOnNotificationAdded_emptyTitle_notLoaded() {
|
||||||
// GIVEN that the manager has a notification with a resume action and empty title.
|
// GIVEN that the manager has a notification with an empty title.
|
||||||
whenever(controller.metadata)
|
whenever(controller.metadata)
|
||||||
.thenReturn(
|
.thenReturn(
|
||||||
metadataBuilder
|
metadataBuilder
|
||||||
.putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
|
.putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
|
||||||
.build()
|
.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()
|
addNotificationAndLoad()
|
||||||
val data = mediaDataCaptor.value
|
val data = mediaDataCaptor.value
|
||||||
val instanceId = data.instanceId
|
val instanceId = data.instanceId
|
||||||
assertThat(data.resumption).isFalse()
|
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
|
// WHEN the notification is removed
|
||||||
reset(listener)
|
reset(listener)
|
||||||
@@ -554,17 +674,15 @@ class MediaDataManagerTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testOnNotificationRemoved_blankTitle_notConverted() {
|
fun testOnNotificationRemoved_blankTitle_notConverted() {
|
||||||
// GIVEN that the manager has a notification with a resume action and blank title.
|
// 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()
|
addNotificationAndLoad()
|
||||||
val data = mediaDataCaptor.value
|
val data = mediaDataCaptor.value
|
||||||
val instanceId = data.instanceId
|
val instanceId = data.instanceId
|
||||||
assertThat(data.resumption).isFalse()
|
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
|
// WHEN the notification is removed
|
||||||
reset(listener)
|
reset(listener)
|
||||||
|
|||||||
Reference in New Issue
Block a user