Only dismiss media notification on user interaction
Calling NotifCollection.dismissNotification causes the deleteIntent for
the notification to be sent. This API is used to tell when the
notification has been explicitly dismissed by the user, so we shouldn't
trigger it when the media control is removed automatically by the system.
Currently, the system only dismisses the notification automatically when
the media session is destroyed, or when the notification is already removed
by other means such as when pausing the app.
User-initated dismissal can happen via the long press menu, or by
swiping away paused media when the resumption setting is off.
Bug: 335875159
Bug: 339904764
Test: atest com.android.systemui.media.controls
Test: manual with test app: verify intent is sent when dismissing via long press
and is not sent when destroying the session
Test: manual, as above with media_controls_refactor flag enabled
Test: manual, turn off resumption, verify intent is sent after swiping
away paused media
Flag: aconfig com.android.systemui.media_controls_user_initiated_dismiss DEVELOPMENT
Change-Id: I49e9b8e5c84383fc3bb2633ec39b71a8f2b9bdb6
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 626e219..0dd956c 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -891,4 +891,14 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
\ No newline at end of file
+}
+
+flag {
+ name: "media_controls_user_initiated_dismiss"
+ namespace: "systemui"
+ description: "Only dismiss media notifications when the control was removed by the user."
+ bug: "335875159"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 1cdc2b6..407bf4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -114,7 +114,7 @@
// Change to media unavailable and notify the listener.
whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
- mediaDataListenerCaptor.value.onMediaDataRemoved("key")
+ mediaDataListenerCaptor.value.onMediaDataRemoved("key", false)
runCurrent()
// Media active now returns false.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
index d9224d7..bd3b77a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
@@ -27,12 +27,16 @@
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.bluetooth.mockBroadcastDialogController
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.data.repository.mediaDataRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
+import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaControlInteractor
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
+import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.util.mediaInstanceId
import com.android.systemui.media.mediaOutputDialogManager
@@ -211,6 +215,21 @@
)
}
+ @Test
+ fun removeMediaControl() {
+ val listener = mock<MediaDataProcessor.Listener>()
+ kosmos.mediaDataProcessor.addInternalListener(listener)
+
+ var mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST)
+ kosmos.mediaDataRepository.addMediaEntry(KEY, mediaData)
+
+ underTest.removeMediaControl(null, instanceId, 0L)
+ kosmos.fakeExecutor.advanceClockToNext()
+ kosmos.fakeExecutor.runAllReady()
+
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
+ }
+
companion object {
private const val USER_ID = 0
private const val KEY = "key"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
new file mode 100644
index 0000000..8cb811d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.service.notification.NotificationListenerService
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.mockNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifPipeline
+import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationMediaManagerTest : SysuiTestCase() {
+
+ private val KEY = "KEY"
+
+ private val kosmos = testKosmos()
+ private val visibilityProvider = kosmos.notificationVisibilityProvider
+ private val notifPipeline = kosmos.notifPipeline
+ private val notifCollection = kosmos.mockNotifCollection
+ private val dumpManager = kosmos.dumpManager
+ private val mediaDataManager = mock<MediaDataManager>()
+ private val backgroundExecutor = FakeExecutor(FakeSystemClock())
+
+ private var listenerCaptor = argumentCaptor<MediaDataManager.Listener>()
+
+ private lateinit var notificationMediaManager: NotificationMediaManager
+
+ @Before
+ fun setup() {
+ notificationMediaManager =
+ NotificationMediaManager(
+ context,
+ visibilityProvider,
+ notifPipeline,
+ notifCollection,
+ mediaDataManager,
+ dumpManager,
+ backgroundExecutor,
+ )
+
+ verify(mediaDataManager).addListener(listenerCaptor.capture())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+ fun mediaDataRemoved_userInitiated_dismissNotif() {
+ val notifEntryCaptor = argumentCaptor<NotificationEntry>()
+ val notifEntry = mock<NotificationEntry>()
+ whenever(notifEntry.key).thenReturn(KEY)
+ whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking())
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry))
+
+ listenerCaptor.lastValue.onMediaDataRemoved(KEY, true)
+
+ verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any())
+ assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+ fun mediaDataRemoved_notUserInitiated_doesNotDismissNotif() {
+ listenerCaptor.lastValue.onMediaDataRemoved(KEY, false)
+
+ verify(notifCollection, never()).dismissNotification(any(), any())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS)
+ fun mediaDataRemoved_notUserInitiated_flagOff_dismissNotif() {
+ val notifEntryCaptor = argumentCaptor<NotificationEntry>()
+ val notifEntry = mock<NotificationEntry>()
+ whenever(notifEntry.key).thenReturn(KEY)
+ whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking())
+ whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry))
+
+ listenerCaptor.lastValue.onMediaDataRemoved(KEY, false)
+
+ verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any())
+ assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e2fed6d..e5a0e50 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -53,7 +53,7 @@
updateMediaModel(data)
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
updateMediaModel()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt
index c02478b..96ef7d2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt
@@ -206,11 +206,11 @@
listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
allEntries.remove(key)
userEntries.remove(key)?.let {
// Only notify listeners if something actually changed
- listeners.forEach { it.onMediaDataRemoved(key) }
+ listeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
}
@@ -246,7 +246,7 @@
// Only remove media when the profile is unavailable.
if (DEBUG) Log.d(TAG, "Removing $key after profile change")
userEntries.remove(key, data)
- listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
+ listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) }
}
}
}
@@ -261,7 +261,7 @@
userEntries.clear()
keyCopy.forEach {
if (DEBUG) Log.d(TAG, "Removing $it after user change")
- listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
+ listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) }
}
allEntries.forEach { (key, data) ->
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 3a831156..143d66b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -545,8 +545,8 @@
* External listeners registered with [addListener] will be notified after the event propagates
* through the internal listener pipeline.
*/
- private fun notifyMediaDataRemoved(key: String) {
- internalListeners.forEach { it.onMediaDataRemoved(key) }
+ private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) {
+ internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
/**
@@ -578,7 +578,7 @@
if (it.active == !timedOut && !forceUpdate) {
if (it.resumption) {
if (DEBUG) Log.d(TAG, "timing out resume player $key")
- dismissMediaData(key, 0L /* delay */)
+ dismissMediaData(key, delay = 0L, userInitiated = false)
}
return
}
@@ -627,17 +627,17 @@
}
}
- private fun removeEntry(key: String, logEvent: Boolean = true) {
+ private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) {
mediaEntries.remove(key)?.let {
if (logEvent) {
logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
}
}
- notifyMediaDataRemoved(key)
+ notifyMediaDataRemoved(key, userInitiated)
}
/** Dismiss a media entry. Returns false if the key was not found. */
- override fun dismissMediaData(key: String, delay: Long): Boolean {
+ override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean {
val existed = mediaEntries[key] != null
backgroundExecutor.execute {
mediaEntries[key]?.let { mediaData ->
@@ -649,7 +649,10 @@
}
}
}
- foregroundExecutor.executeDelayed({ removeEntry(key) }, delay)
+ foregroundExecutor.executeDelayed(
+ { removeEntry(key = key, userInitiated = userInitiated) },
+ delay
+ )
return existed
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
index ad70db5..88910f9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
@@ -53,8 +53,8 @@
listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) }
}
- override fun onMediaDataRemoved(key: String) {
- remove(key)
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
+ remove(key, userInitiated)
}
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
@@ -71,8 +71,8 @@
}
}
- override fun onKeyRemoved(key: String) {
- remove(key)
+ override fun onKeyRemoved(key: String, userInitiated: Boolean) {
+ remove(key, userInitiated)
}
/**
@@ -92,10 +92,10 @@
}
}
- private fun remove(key: String) {
+ private fun remove(key: String, userInitiated: Boolean) {
entries.remove(key)?.let {
val listenersCopy = listeners.toSet()
- listenersCopy.forEach { it.onMediaDataRemoved(key) }
+ listenersCopy.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
index 5432a18..8d19ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
@@ -213,7 +213,7 @@
listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
mediaFilterRepository.removeMediaEntry(key)?.let { mediaData ->
val instanceId = mediaData.instanceId
mediaFilterRepository.removeSelectedUserMediaEntry(instanceId)?.let {
@@ -221,7 +221,7 @@
MediaDataLoadingModel.Removed(instanceId)
)
// Only notify listeners if something actually changed
- listeners.forEach { it.onMediaDataRemoved(key) }
+ listeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
}
}
@@ -270,7 +270,7 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Removed(data.instanceId)
)
- listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
+ listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) }
}
}
}
@@ -288,7 +288,7 @@
MediaDataLoadingModel.Removed(instanceId)
)
getKey(instanceId)?.let {
- listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
+ listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
index 2331aa21..8099e59 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
@@ -60,7 +60,7 @@
)
/** Dismiss a media entry. Returns false if the key was not found. */
- fun dismissMediaData(key: String, delay: Long): Boolean
+ fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean
/**
* Called whenever the recommendation has been expired or removed by the user. This will remove
@@ -136,7 +136,7 @@
) {}
/** Called whenever a previously existing Media notification was removed. */
- override fun onMediaDataRemoved(key: String) {}
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {}
/**
* Called whenever a previously existing Smartspace media data was removed.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index 1d7c025..eed7752 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -498,8 +498,8 @@
* External listeners registered with [MediaCarouselInteractor.addListener] will be notified
* after the event propagates through the internal listener pipeline.
*/
- private fun notifyMediaDataRemoved(key: String) {
- internalListeners.forEach { it.onMediaDataRemoved(key) }
+ private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) {
+ internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) }
}
/**
@@ -531,7 +531,7 @@
if (it.active == !timedOut && !forceUpdate) {
if (it.resumption) {
if (DEBUG) Log.d(TAG, "timing out resume player $key")
- dismissMediaData(key, 0L /* delay */)
+ dismissMediaData(key, delayMs = 0L, userInitiated = false)
}
return
}
@@ -580,17 +580,17 @@
}
}
- private fun removeEntry(key: String, logEvent: Boolean = true) {
+ private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) {
mediaDataRepository.removeMediaEntry(key)?.let {
if (logEvent) {
logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
}
}
- notifyMediaDataRemoved(key)
+ notifyMediaDataRemoved(key, userInitiated)
}
/** Dismiss a media entry. Returns false if the key was not found. */
- fun dismissMediaData(key: String, delayMs: Long): Boolean {
+ fun dismissMediaData(key: String, delayMs: Long, userInitiated: Boolean): Boolean {
val existed = mediaDataRepository.mediaEntries.value[key] != null
backgroundExecutor.execute {
mediaDataRepository.mediaEntries.value[key]?.let { mediaData ->
@@ -602,16 +602,19 @@
}
}
}
- foregroundExecutor.executeDelayed({ removeEntry(key) }, delayMs)
+ foregroundExecutor.executeDelayed(
+ { removeEntry(key, userInitiated = userInitiated) },
+ delayMs
+ )
return existed
}
/** Dismiss a media entry. Returns false if the corresponding key was not found. */
- fun dismissMediaData(instanceId: InstanceId, delayMs: Long): Boolean {
+ fun dismissMediaData(instanceId: InstanceId, delayMs: Long, userInitiated: Boolean): Boolean {
val mediaEntries = mediaDataRepository.mediaEntries.value
val filteredEntries = mediaEntries.filter { (_, data) -> data.instanceId == instanceId }
return if (filteredEntries.isNotEmpty()) {
- dismissMediaData(filteredEntries.keys.first(), delayMs)
+ dismissMediaData(filteredEntries.keys.first(), delayMs, userInitiated)
} else {
false
}
@@ -1579,7 +1582,7 @@
) {}
/** Called whenever a previously existing Media notification was removed. */
- fun onMediaDataRemoved(key: String) {}
+ fun onMediaDataRemoved(key: String, userInitiated: Boolean) {}
/**
* Called whenever a previously existing Smartspace media data was removed.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index 0e2814b..043fbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -111,10 +111,10 @@
}
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
val token = entries.remove(key)
token?.stop()
- token?.let { listeners.forEach { it.onKeyRemoved(key) } }
+ token?.let { listeners.forEach { it.onKeyRemoved(key, userInitiated) } }
}
fun dump(pw: PrintWriter) {
@@ -136,7 +136,7 @@
/** Called when the route has changed for a given notification. */
fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?)
/** Called when the notification was removed. */
- fun onKeyRemoved(key: String)
+ fun onKeyRemoved(key: String, userInitiated: Boolean)
}
private inner class Entry(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
index b2a8f2e..b178d84 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
@@ -137,7 +137,7 @@
// farther and dismiss the media data so that media controls for the local session
// don't hang around while casting.
if (!keyedTokens.get(key)!!.contains(TokenId(remote.sessionToken))) {
- dispatchMediaDataRemoved(key)
+ dispatchMediaDataRemoved(key, userInitiated = false)
}
}
}
@@ -151,11 +151,11 @@
backgroundExecutor.execute { dispatchSmartspaceMediaDataLoaded(key, data) }
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
// Queue on background thread to ensure ordering of loaded and removed events is maintained.
backgroundExecutor.execute {
keyedTokens.remove(key)
- dispatchMediaDataRemoved(key)
+ dispatchMediaDataRemoved(key, userInitiated)
}
}
@@ -174,8 +174,10 @@
}
}
- private fun dispatchMediaDataRemoved(key: String) {
- foregroundExecutor.execute { listeners.toSet().forEach { it.onMediaDataRemoved(key) } }
+ private fun dispatchMediaDataRemoved(key: String, userInitiated: Boolean) {
+ foregroundExecutor.execute {
+ listeners.toSet().forEach { it.onMediaDataRemoved(key, userInitiated) }
+ }
}
private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
index 29f3967..fc31903 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
@@ -169,7 +169,7 @@
mediaListeners[key] = PlaybackStateListener(key, data)
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
mediaListeners.remove(key)?.destroy()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index c888935..9e62300 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -205,12 +205,12 @@
)
}
- override fun dismissMediaData(key: String, delay: Long): Boolean {
- return mediaDataProcessor.dismissMediaData(key, delay)
+ override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean {
+ return mediaDataProcessor.dismissMediaData(key, delay, userInitiated)
}
fun removeMediaControl(instanceId: InstanceId, delay: Long) {
- mediaDataProcessor.dismissMediaData(instanceId, delay)
+ mediaDataProcessor.dismissMediaData(instanceId, delay, userInitiated = false)
}
override fun dismissSmartspaceRecommendation(key: String, delay: Long) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
index 9f2d132..d1fee90 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
@@ -90,7 +90,8 @@
instanceId: InstanceId,
delayMs: Long
): Boolean {
- val dismissed = mediaDataProcessor.dismissMediaData(instanceId, delayMs)
+ val dismissed =
+ mediaDataProcessor.dismissMediaData(instanceId, delayMs, userInitiated = true)
if (!dismissed) {
Log.w(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 45b68ca..b072534 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -193,6 +193,7 @@
private val mediaContent: ViewGroup
@VisibleForTesting var pageIndicator: PageIndicator
private var needsReordering: Boolean = false
+ private var isUserInitiatedRemovalQueued: Boolean = false
private var keysNeedRemoval = mutableSetOf<String>()
var shouldScrollToKey: Boolean = false
private var isRtl: Boolean = false
@@ -385,12 +386,15 @@
reorderAllPlayers(previousVisiblePlayerKey = null)
}
- keysNeedRemoval.forEach { removePlayer(it) }
+ keysNeedRemoval.forEach {
+ removePlayer(it, userInitiated = isUserInitiatedRemovalQueued)
+ }
if (keysNeedRemoval.size > 0) {
// Carousel visibility may need to be updated after late removals
updateHostVisibility()
}
keysNeedRemoval.clear()
+ isUserInitiatedRemovalQueued = false
// Update user visibility so that no extra impression will be logged when
// activeMediaIndex resets to 0
@@ -474,18 +478,18 @@
val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active
if (canRemove && !Utils.useMediaResumption(context)) {
- // This view isn't playing, let's remove this! This happens e.g. when
- // dismissing/timing out a view. We still have the data around because
- // resumption could be on, but we should save the resources and release
- // this.
+ // This media control is both paused and timed out, and the resumption
+ // setting is off - let's remove it
if (isReorderingAllowed) {
- onMediaDataRemoved(key)
+ onMediaDataRemoved(key, userInitiated = MediaPlayerData.isSwipedAway)
} else {
+ isUserInitiatedRemovalQueued = MediaPlayerData.isSwipedAway
keysNeedRemoval.add(key)
}
} else {
keysNeedRemoval.remove(key)
}
+ MediaPlayerData.isSwipedAway = false
}
override fun onSmartspaceMediaDataLoaded(
@@ -565,11 +569,12 @@
addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
}
}
+ MediaPlayerData.isSwipedAway = false
}
- override fun onMediaDataRemoved(key: String) {
- debugLogger.logMediaRemoved(key)
- removePlayer(key)
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
+ debugLogger.logMediaRemoved(key, userInitiated)
+ removePlayer(key, userInitiated = userInitiated)
}
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
@@ -1033,7 +1038,8 @@
fun removePlayer(
key: String,
dismissMediaData: Boolean = true,
- dismissRecommendation: Boolean = true
+ dismissRecommendation: Boolean = true,
+ userInitiated: Boolean = false,
): MediaControlPanel? {
if (key == MediaPlayerData.smartspaceMediaKey()) {
MediaPlayerData.smartspaceMediaData?.let {
@@ -1052,7 +1058,7 @@
if (dismissMediaData) {
// Inform the media manager of a potentially late dismissal
- mediaManager.dismissMediaData(key, delay = 0L)
+ mediaManager.dismissMediaData(key, delay = 0L, userInitiated = userInitiated)
}
if (dismissRecommendation) {
// Inform the media manager of a potentially late dismissal
@@ -1512,7 +1518,8 @@
}
}
- private fun onSwipeToDismiss() {
+ @VisibleForTesting
+ fun onSwipeToDismiss() {
if (mediaFlags.isMediaControlsRefactorEnabled()) {
mediaCarouselViewModel.onSwipeToDismiss()
return
@@ -1531,6 +1538,7 @@
it.mIsImpressed = false
}
}
+ MediaPlayerData.isSwipedAway = true
logger.logSwipeDismiss()
mediaManager.onSwipeToDismiss()
}
@@ -1557,6 +1565,7 @@
"state: ${desiredHostState?.expansion}, " +
"only active ${desiredHostState?.showsOnlyActiveMedia}"
)
+ println("isSwipedAway: ${MediaPlayerData.isSwipedAway}")
}
}
}
@@ -1595,7 +1604,7 @@
val data: MediaData,
val key: String,
val updateTime: Long = 0,
- val isSsReactivated: Boolean = false
+ val isSsReactivated: Boolean = false,
)
private val comparator =
@@ -1620,6 +1629,9 @@
// A map that tracks order of visible media players before they get reordered.
private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>()
+ // Whether the user swiped away the carousel since its last update
+ internal var isSwipedAway: Boolean = false
+
fun addMediaPlayer(
key: String,
data: MediaData,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
index ebf1c6a..1be25a7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
@@ -53,8 +53,16 @@
{ "add player $str1, active: $bool1" }
)
- fun logMediaRemoved(key: String) =
- buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" })
+ fun logMediaRemoved(key: String, userInitiated: Boolean) =
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = key
+ bool1 = userInitiated
+ },
+ { "removing player $str1, by user $bool1" }
+ )
fun logRecommendationLoaded(key: String, isActive: Boolean) =
buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index e6c785e..0bc3c439 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -786,10 +786,11 @@
if (mKey != null) {
closeGuts();
if (!mMediaDataManagerLazy.get().dismissMediaData(mKey,
- MediaViewController.GUTS_ANIMATION_DURATION + 100)) {
+ /* delay */ MediaViewController.GUTS_ANIMATION_DURATION + 100,
+ /* userInitiated */ true)) {
Log.w(TAG, "Manager failed to dismiss media " + mKey);
// Remove directly from carousel so user isn't stuck with defunct controls
- mMediaCarouselController.removePlayer(mKey, false, false);
+ mMediaCarouselController.removePlayer(mKey, false, false, true);
}
} else {
Log.w(TAG, "Dismiss media with null notification. Token uid="
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
index eca76b6..91050c8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
@@ -105,7 +105,7 @@
updateViewVisibility()
}
- override fun onMediaDataRemoved(key: String) {
+ override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {
updateViewVisibility()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
index 88a5f78..061e7ec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -48,7 +48,7 @@
}
@Override
- public void onMediaDataRemoved(@NonNull String key) {
+ public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) {
final boolean hasActiveMedia = mMediaDataManager.hasActiveMedia();
if (DEBUG) {
Log.d(TAG, "onMediaDataRemoved(" + key + "), mAdded=" + mAdded + ", hasActiveMedia="
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index d7d3732..5bf2f41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.statusbar;
+import static com.android.systemui.Flags.mediaControlsUserInitiatedDismiss;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
@@ -175,14 +177,18 @@
}
@Override
- public void onMediaDataRemoved(@NonNull String key) {
+ public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) {
+ if (mediaControlsUserInitiatedDismiss() && !userInitiated) {
+ // Dismissing the notification will send the app's deleteIntent, so ignore if
+ // this was an automatic removal
+ Log.d(TAG, "Not dismissing " + key + " because it was removed by the system");
+ return;
+ }
mNotifPipeline.getAllNotifs()
.stream()
.filter(entry -> Objects.equals(entry.getKey(), key))
.findAny()
.ifPresent(entry -> {
- // TODO(b/160713608): "removing" this notification won't happen and
- // won't send the 'deleteIntent' if the notification is ongoing.
mNotifCollection.dismissNotification(entry,
getDismissedByUserStats(entry));
});
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
index e56a253..265ade3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
@@ -172,20 +172,20 @@
fun testOnRemovedForCurrent_callsListener() {
// GIVEN a media was removed for main user
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
// THEN we should tell the listener
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
fun testOnRemovedForGuest_doesNotCallListener() {
// GIVEN a media was removed for guest user
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
// THEN we should NOT tell the listener
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
}
@Test
@@ -197,7 +197,7 @@
setUser(USER_GUEST)
// THEN we should remove the main user's media
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -230,7 +230,7 @@
setPrivateProfileUnavailable()
// THEN we should add the private profile media
- verify(listener).onMediaDataRemoved(eq(KEY_ALT))
+ verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false))
}
@Test
@@ -360,7 +360,7 @@
@Test
fun testOnNotificationRemoved_doesntHaveMedia() {
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse()
assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index 5a2d22d..99bf2db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -346,7 +346,7 @@
// THEN it is removed and listeners are informed
foregroundExecutor.advanceClockToLast()
foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(PACKAGE_NAME)
+ verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
}
@Test
@@ -532,7 +532,7 @@
addNotificationAndLoad()
val data = mediaDataCaptor.value
mediaDataManager.onNotificationRemoved(KEY)
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@@ -777,7 +777,7 @@
eq(false)
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
// WHEN the second is removed
mediaDataManager.onNotificationRemoved(KEY_2)
// THEN the data is for resumption and the second key is removed
@@ -791,7 +791,7 @@
eq(false)
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
- verify(listener).onMediaDataRemoved(eq(KEY_2))
+ verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false))
}
@Test
@@ -816,7 +816,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// THEN the media data is removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -866,7 +866,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// THEN the media data is removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -905,7 +905,7 @@
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
// And the oldest resume control was removed
- verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"))
+ verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"), eq(false))
}
fun testOnNotificationRemoved_lockDownMode() {
@@ -915,7 +915,7 @@
val data = mediaDataCaptor.value
mediaDataManager.onNotificationRemoved(KEY)
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(logger, never())
.logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
@@ -1148,7 +1148,7 @@
mediaDataManager.setMediaResumptionEnabled(false)
// THEN the resume controls are dismissed
- verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME))
+ verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@@ -1156,19 +1156,19 @@
fun testDismissMedia_listenerCalled() {
addNotificationAndLoad()
val data = mediaDataCaptor.value
- val removed = mediaDataManager.dismissMediaData(KEY, 0L)
+ val removed = mediaDataManager.dismissMediaData(KEY, 0L, true)
assertThat(removed).isTrue()
foregroundExecutor.advanceClockToLast()
foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
fun testDismissMedia_keyDoesNotExist_returnsFalse() {
- val removed = mediaDataManager.dismissMediaData(KEY, 0L)
+ val removed = mediaDataManager.dismissMediaData(KEY, 0L, true)
assertThat(removed).isFalse()
}
@@ -2077,7 +2077,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It remains as a regular player
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2093,7 +2093,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2146,7 +2146,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// It remains as a regular player
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2199,7 +2199,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2253,7 +2253,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed.
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(
@@ -2279,7 +2279,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2329,7 +2329,7 @@
mediaDataManager.onNotificationRemoved(KEY)
// We still make sure to remove it
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
index bb5b572..dd05a0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
@@ -202,24 +202,24 @@
@Test
public void mediaDataRemoved() {
// WHEN media data is removed without first receiving device or data
- mManager.onMediaDataRemoved(KEY);
+ mManager.onMediaDataRemoved(KEY, false);
// THEN a removed event isn't emitted
- verify(mListener, never()).onMediaDataRemoved(eq(KEY));
+ verify(mListener, never()).onMediaDataRemoved(eq(KEY), anyBoolean());
}
@Test
public void mediaDataRemovedAfterMediaEvent() {
mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
- mManager.onMediaDataRemoved(KEY);
- verify(mListener).onMediaDataRemoved(eq(KEY));
+ mManager.onMediaDataRemoved(KEY, false);
+ verify(mListener).onMediaDataRemoved(eq(KEY), eq(false));
}
@Test
public void mediaDataRemovedAfterDeviceEvent() {
mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
- mManager.onMediaDataRemoved(KEY);
- verify(mListener).onMediaDataRemoved(eq(KEY));
+ mManager.onMediaDataRemoved(KEY, false);
+ verify(mListener).onMediaDataRemoved(eq(KEY), eq(false));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index 77ad263..35eefd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -205,9 +205,9 @@
assertThat(currentMedia).containsExactly(mediaCommonModel)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
assertThat(currentMedia).doesNotContain(mediaCommonModel)
}
@@ -218,9 +218,9 @@
// GIVEN a media was removed for guest user
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
assertThat(currentMedia).isEmpty()
}
@@ -239,7 +239,7 @@
setUser(USER_GUEST)
// THEN we should remove the main user's media
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
assertThat(currentMedia).isEmpty()
}
@@ -291,7 +291,7 @@
val mediaLoadedStatesModel = MediaDataLoadingModel.Loaded(dataMain.instanceId)
// THEN we should remove the private profile media
- verify(listener).onMediaDataRemoved(eq(KEY_ALT))
+ verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false))
assertThat(currentMedia)
.containsExactly(MediaCommonModel.MediaControl(mediaLoadedStatesModel))
}
@@ -502,7 +502,7 @@
val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ mediaDataFilter.onMediaDataRemoved(KEY, false)
assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
.isFalse()
assertThat(hasAnyMedia(selectedUserEntries)).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 1de7ee3..5791826 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -384,7 +384,7 @@
// THEN it is removed and listeners are informed
foregroundExecutor.advanceClockToLast()
foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(PACKAGE_NAME)
+ verify(listener).onMediaDataRemoved(PACKAGE_NAME, false)
}
@Test
@@ -567,7 +567,7 @@
addNotificationAndLoad()
val data = mediaDataCaptor.value
mediaDataProcessor.onNotificationRemoved(KEY)
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@@ -812,7 +812,7 @@
eq(false)
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
// WHEN the second is removed
mediaDataProcessor.onNotificationRemoved(KEY_2)
// THEN the data is for resumption and the second key is removed
@@ -826,7 +826,7 @@
eq(false)
)
assertThat(mediaDataCaptor.value.resumption).isTrue()
- verify(listener).onMediaDataRemoved(eq(KEY_2))
+ verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false))
}
@Test
@@ -851,7 +851,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// THEN the media data is removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -901,7 +901,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// THEN the media data is removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -940,7 +940,7 @@
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
// And the oldest resume control was removed
- verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"))
+ verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"), eq(false))
}
fun testOnNotificationRemoved_lockDownMode() {
@@ -950,7 +950,7 @@
val data = mediaDataCaptor.value
mediaDataProcessor.onNotificationRemoved(KEY)
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger, never())
.logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
@@ -1183,7 +1183,7 @@
mediaDataProcessor.setMediaResumptionEnabled(false)
// THEN the resume controls are dismissed
- verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME))
+ verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@@ -1191,19 +1191,19 @@
fun testDismissMedia_listenerCalled() {
addNotificationAndLoad()
val data = mediaDataCaptor.value
- val removed = mediaDataProcessor.dismissMediaData(KEY, 0L)
+ val removed = mediaDataProcessor.dismissMediaData(KEY, 0L, true)
assertThat(removed).isTrue()
foregroundExecutor.advanceClockToLast()
foregroundExecutor.runAllReady()
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
fun testDismissMedia_keyDoesNotExist_returnsFalse() {
- val removed = mediaDataProcessor.dismissMediaData(KEY, 0L)
+ val removed = mediaDataProcessor.dismissMediaData(KEY, 0L, true)
assertThat(removed).isFalse()
}
@@ -2102,7 +2102,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It remains as a regular player
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2118,7 +2118,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2171,7 +2171,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// It remains as a regular player
- verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean())
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2224,7 +2224,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2278,7 +2278,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed.
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(
@@ -2304,7 +2304,7 @@
sessionCallbackCaptor.value.invoke(KEY)
// It is fully removed
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
verify(listener, never())
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
@@ -2354,7 +2354,7 @@
mediaDataProcessor.onNotificationRemoved(KEY)
// We still make sure to remove it
- verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(listener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index a447e44..befe64c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -60,6 +60,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.any
@@ -158,7 +159,8 @@
@Test
fun removeUnknown() {
- manager.onMediaDataRemoved("unknown")
+ manager.onMediaDataRemoved("unknown", false)
+ verify(listener, never()).onKeyRemoved(eq(KEY), anyBoolean())
}
@Test
@@ -170,7 +172,7 @@
@Test
fun loadAndRemoveMediaData() {
manager.onMediaDataLoaded(KEY, null, mediaData)
- manager.onMediaDataRemoved(KEY)
+ manager.onMediaDataRemoved(KEY, false)
fakeBgExecutor.runAllReady()
verify(lmm).unregisterCallback(any())
verify(muteAwaitManager).stopListening()
@@ -386,9 +388,9 @@
fun listenerReceivesKeyRemoved() {
manager.onMediaDataLoaded(KEY, null, mediaData)
// WHEN the notification is removed
- manager.onMediaDataRemoved(KEY)
+ manager.onMediaDataRemoved(KEY, true)
// THEN the listener receives key removed event
- verify(listener).onKeyRemoved(eq(KEY))
+ verify(listener).onKeyRemoved(eq(KEY), eq(true))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
index 5a3c220..030bca2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
@@ -165,10 +165,10 @@
@Test
fun noMediaSession_removedEventNotFiltered() {
- filter.onMediaDataRemoved(KEY)
+ filter.onMediaDataRemoved(KEY, false)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
- verify(mediaListener).onMediaDataRemoved(eq(KEY))
+ verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -193,11 +193,11 @@
whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
sessionListener.onActiveSessionsChanged(controllers)
// WHEN a removed event is received
- filter.onMediaDataRemoved(KEY)
+ filter.onMediaDataRemoved(KEY, false)
bgExecutor.runAllReady()
fgExecutor.runAllReady()
// THEN the event is not filtered
- verify(mediaListener).onMediaDataRemoved(eq(KEY))
+ verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false))
}
@Test
@@ -294,7 +294,7 @@
anyBoolean()
)
// AND there should be a removed event for key2
- verify(mediaListener).onMediaDataRemoved(eq(key2))
+ verify(mediaListener).onMediaDataRemoved(eq(key2), eq(false))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
index 3cc65c9..cdbf9d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
@@ -166,12 +166,12 @@
@Test
fun testOnMediaDataRemoved_unregistersPlaybackListener() {
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
- mediaTimeoutListener.onMediaDataRemoved(KEY)
+ mediaTimeoutListener.onMediaDataRemoved(KEY, false)
verify(mediaController).unregisterCallback(anyObject())
// Ignores duplicate requests
clearInvocations(mediaController)
- mediaTimeoutListener.onMediaDataRemoved(KEY)
+ mediaTimeoutListener.onMediaDataRemoved(KEY, false)
verify(mediaController, never()).unregisterCallback(anyObject())
}
@@ -181,7 +181,7 @@
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
assertThat(executor.numPending()).isEqualTo(1)
// WHEN the media is removed
- mediaTimeoutListener.onMediaDataRemoved(KEY)
+ mediaTimeoutListener.onMediaDataRemoved(KEY, false)
// THEN the timeout runnable is cancelled
assertThat(executor.numPending()).isEqualTo(0)
}
@@ -398,7 +398,7 @@
// WHEN we have a resume control
testOnMediaDataLoaded_resumption_registersTimeout()
// AND the media is removed
- mediaTimeoutListener.onMediaDataRemoved(PACKAGE)
+ mediaTimeoutListener.onMediaDataRemoved(PACKAGE, false)
// THEN the timeout runnable is cancelled
assertThat(executor.numPending()).isEqualTo(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 0a5aace..3bb8b8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -33,6 +33,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -74,12 +76,14 @@
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
+import org.junit.After
import org.junit.Before
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.anyLong
import org.mockito.Mockito.floatThat
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -136,6 +140,9 @@
private lateinit var testDispatcher: TestDispatcher
private lateinit var mediaCarouselController: MediaCarouselController
+ private var originalResumeSetting =
+ Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -186,6 +193,15 @@
)
}
+ @After
+ fun tearDown() {
+ Settings.Secure.putInt(
+ context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RESUME,
+ originalResumeSetting
+ )
+ }
+
@Test
fun testPlayerOrdering() {
// Test values: key, data, last active time
@@ -822,6 +838,7 @@
@Test
fun testKeyguardGone_showMediaCarousel() =
kosmos.testScope.runTest {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
var updatedVisibility = false
mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
mediaCarouselController.mediaCarousel = mediaCarousel
@@ -844,6 +861,7 @@
@Test
fun keyguardShowing_notAllowedOnLockscreen_updateVisibility() {
kosmos.testScope.runTest {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
var updatedVisibility = false
mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
mediaCarouselController.mediaCarousel = mediaCarousel
@@ -870,6 +888,7 @@
@Test
fun keyguardShowing_allowedOnLockscreen_updateVisibility() {
kosmos.testScope.runTest {
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
var updatedVisibility = false
mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
mediaCarouselController.mediaCarousel = mediaCarousel
@@ -968,6 +987,45 @@
verify(panel).updateAnimatorDurationScale()
}
+ @Test
+ fun swipeToDismiss_pausedAndResumeOff_userInitiated() {
+ // When resumption is disabled, paused media should be dismissed after being swiped away
+ Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+
+ val pausedMedia = DATA.copy(isPlaying = false)
+ listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
+ mediaCarouselController.onSwipeToDismiss()
+
+ // When it can be removed immediately on update
+ whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
+ val inactiveMedia = pausedMedia.copy(active = false)
+ listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia)
+
+ // This is processed as a user-initiated dismissal
+ verify(debugLogger).logMediaRemoved(eq(PAUSED_LOCAL), eq(true))
+ verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true))
+ }
+
+ @Test
+ fun swipeToDismiss_pausedAndResumeOff_delayed_userInitiated() {
+ // When resumption is disabled, paused media should be dismissed after being swiped away
+ Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+ mediaCarouselController.updateHostVisibility = {}
+
+ val pausedMedia = DATA.copy(isPlaying = false)
+ listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
+ mediaCarouselController.onSwipeToDismiss()
+
+ // When it can't be removed immediately on update
+ whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false)
+ val inactiveMedia = pausedMedia.copy(active = false)
+ listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia)
+ visualStabilityCallback.value.onReorderingAllowed()
+
+ // This is processed as a user-initiated dismissal
+ verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true))
+ }
+
/**
* Helper method when a configuration change occurs.
*
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 83e4d31..0c9fee9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -1344,7 +1344,7 @@
assertThat(dismiss.isEnabled).isEqualTo(true)
dismiss.callOnClick()
verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId))
- verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
+ verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong(), eq(true))
}
@Test
@@ -1360,7 +1360,8 @@
@Test
fun player_dismissButtonClick_notInManager() {
val mediaKey = "key for dismissal"
- whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false)
+ whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong(), eq(true)))
+ .thenReturn(false)
player.attachPlayer(viewHolder)
val state = mediaData.copy(notificationKey = KEY)
@@ -1369,8 +1370,8 @@
assertThat(dismiss.isEnabled).isEqualTo(true)
dismiss.callOnClick()
- verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
- verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false))
+ verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong(), eq(true))
+ verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false), eq(true))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index ff7c970..8f8630e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -104,11 +104,11 @@
listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */true,
/* receivedSmartspaceCardLatency= */0, /* isSsReactived= */ false);
- listener.onMediaDataRemoved(mKey);
+ listener.onMediaDataRemoved(mKey, false);
verify(mDreamOverlayStateController, never()).removeComplication(any());
when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
- listener.onMediaDataRemoved(mKey);
+ listener.onMediaDataRemoved(mKey, false);
verify(mDreamOverlayStateController).removeComplication(eq(mMediaEntryComplication));
}