Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit edf88869 authored by Robert Snoeberger's avatar Robert Snoeberger Committed by Android (Google) Code Review
Browse files

Merge "Filter out notif updates based on active media session"

parents 5c723d25 0e098127
Loading
Loading
Loading
Loading
+6 −0
Original line number Original line Diff line number Diff line
@@ -43,6 +43,7 @@ import android.hardware.display.ColorDisplayManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.media.AudioManager;
import android.media.MediaRouter2Manager;
import android.media.MediaRouter2Manager;
import android.media.session.MediaSessionManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager;
import android.net.NetworkScoreManager;
import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager;
@@ -225,6 +226,11 @@ public class SystemServicesModule {
        return MediaRouter2Manager.getInstance(context);
        return MediaRouter2Manager.getInstance(context);
    }
    }


    @Provides
    static MediaSessionManager provideMediaSessionManager(Context context) {
        return context.getSystemService(MediaSessionManager.class);
    }

    @Provides
    @Provides
    @Singleton
    @Singleton
    static NetworkScoreManager provideNetworkScoreManager(Context context) {
    static NetworkScoreManager provideNetworkScoreManager(Context context) {
+1 −1
Original line number Original line Diff line number Diff line
@@ -42,7 +42,7 @@ class MediaCarouselController @Inject constructor(
    private val mediaHostStatesManager: MediaHostStatesManager,
    private val mediaHostStatesManager: MediaHostStatesManager,
    private val activityStarter: ActivityStarter,
    private val activityStarter: ActivityStarter,
    @Main executor: DelayableExecutor,
    @Main executor: DelayableExecutor,
    mediaManager: MediaDataFilter,
    mediaManager: MediaDataManager,
    configurationController: ConfigurationController,
    configurationController: ConfigurationController,
    falsingManager: FalsingManager
    falsingManager: FalsingManager
) {
) {
+31 −48
Original line number Original line Diff line number Diff line
@@ -17,21 +17,16 @@
package com.android.systemui.media
package com.android.systemui.media


import javax.inject.Inject
import javax.inject.Inject
import javax.inject.Singleton


/**
/**
 * Combines updates from [MediaDataManager] with [MediaDeviceManager].
 * Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events.
 */
 */
@Singleton
class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
class MediaDataCombineLatest @Inject constructor(
        MediaDeviceManager.Listener {
    private val dataSource: MediaDataManager,

    private val deviceSource: MediaDeviceManager
) {
    private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
    private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
    private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf()
    private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf()


    init {
        dataSource.addListener(object : MediaDataManager.Listener {
    override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
    override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
        if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
        if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
            entries[key] = data to entries.remove(oldKey)?.second
            entries[key] = data to entries.remove(oldKey)?.second
@@ -41,11 +36,11 @@ class MediaDataCombineLatest @Inject constructor(
            update(key, key)
            update(key, key)
        }
        }
    }
    }

    override fun onMediaDataRemoved(key: String) {
    override fun onMediaDataRemoved(key: String) {
        remove(key)
        remove(key)
    }
    }
        })

        deviceSource.addListener(object : MediaDeviceManager.Listener {
    override fun onMediaDeviceChanged(
    override fun onMediaDeviceChanged(
        key: String,
        key: String,
        oldKey: String?,
        oldKey: String?,
@@ -59,22 +54,10 @@ class MediaDataCombineLatest @Inject constructor(
            update(key, key)
            update(key, key)
        }
        }
    }
    }

    override fun onKeyRemoved(key: String) {
    override fun onKeyRemoved(key: String) {
        remove(key)
        remove(key)
    }
    }
        })
    }

    /**
     * Get a map of all non-null data entries
     */
    fun getData(): Map<String, MediaData> {
        return entries.filter {
            (key, pair) -> pair.first != null && pair.second != null
        }.mapValues {
            (key, pair) -> pair.first!!.copy(device = pair.second)
        }
    }


    /**
    /**
     * Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData].
     * Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData].
+32 −30
Original line number Original line Diff line number Diff line
@@ -24,7 +24,6 @@ import com.android.systemui.settings.CurrentUserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import java.util.concurrent.Executor
import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Inject
import javax.inject.Singleton


private const val TAG = "MediaDataFilter"
private const val TAG = "MediaDataFilter"
private const val DEBUG = true
private const val DEBUG = true
@@ -33,24 +32,24 @@ private const val DEBUG = true
 * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user
 * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user
 * switches (removing entries for the previous user, adding back entries for the current user)
 * switches (removing entries for the previous user, adding back entries for the current user)
 *
 *
 * This is added downstream of [MediaDataManager] since we may still need to handle callbacks from
 * This is added at the end of the pipeline since we may still need to handle callbacks from
 * background users (e.g. timeouts) that UI classes should ignore.
 * background users (e.g. timeouts).
 * Instead, UI classes should listen to this so they can stay in sync with the current user.
 */
 */
@Singleton
class MediaDataFilter @Inject constructor(
class MediaDataFilter @Inject constructor(
    private val dataSource: MediaDataCombineLatest,
    private val broadcastDispatcher: BroadcastDispatcher,
    private val broadcastDispatcher: BroadcastDispatcher,
    private val mediaResumeListener: MediaResumeListener,
    private val mediaResumeListener: MediaResumeListener,
    private val mediaDataManager: MediaDataManager,
    private val lockscreenUserManager: NotificationLockscreenUserManager,
    private val lockscreenUserManager: NotificationLockscreenUserManager,
    @Main private val executor: Executor
    @Main private val executor: Executor
) : MediaDataManager.Listener {
) : MediaDataManager.Listener {
    private val userTracker: CurrentUserTracker
    private val userTracker: CurrentUserTracker
    private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
    private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
    internal val listeners: Set<MediaDataManager.Listener>
        get() = _listeners.toSet()
    internal lateinit var mediaDataManager: MediaDataManager


    // The filtered mediaEntries, which will be a subset of all mediaEntries in MediaDataManager
    private val allEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
    private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
    // The filtered userEntries, which will be a subset of all userEntries in MediaDataManager
    private val userEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()


    init {
    init {
        userTracker = object : CurrentUserTracker(broadcastDispatcher) {
        userTracker = object : CurrentUserTracker(broadcastDispatcher) {
@@ -60,31 +59,34 @@ class MediaDataFilter @Inject constructor(
            }
            }
        }
        }
        userTracker.startTracking()
        userTracker.startTracking()
        dataSource.addListener(this)
    }
    }


    override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
    override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
        if (oldKey != null && oldKey != key) {
            allEntries.remove(oldKey)
        }
        allEntries.put(key, data)

        if (!lockscreenUserManager.isCurrentProfile(data.userId)) {
        if (!lockscreenUserManager.isCurrentProfile(data.userId)) {
            return
            return
        }
        }


        if (oldKey != null) {
        if (oldKey != null && oldKey != key) {
            mediaEntries.remove(oldKey)
            userEntries.remove(oldKey)
        }
        }
        mediaEntries.put(key, data)
        userEntries.put(key, data)


        // Notify listeners
        // Notify listeners
        val listenersCopy = listeners.toSet()
        listeners.forEach {
        listenersCopy.forEach {
            it.onMediaDataLoaded(key, oldKey, data)
            it.onMediaDataLoaded(key, oldKey, data)
        }
        }
    }
    }


    override fun onMediaDataRemoved(key: String) {
    override fun onMediaDataRemoved(key: String) {
        mediaEntries.remove(key)?.let {
        allEntries.remove(key)
        userEntries.remove(key)?.let {
            // Only notify listeners if something actually changed
            // Only notify listeners if something actually changed
            val listenersCopy = listeners.toSet()
            listeners.forEach {
            listenersCopy.forEach {
                it.onMediaDataRemoved(key)
                it.onMediaDataRemoved(key)
            }
            }
        }
        }
@@ -93,11 +95,11 @@ class MediaDataFilter @Inject constructor(
    @VisibleForTesting
    @VisibleForTesting
    internal fun handleUserSwitched(id: Int) {
    internal fun handleUserSwitched(id: Int) {
        // If the user changes, remove all current MediaData objects and inform listeners
        // If the user changes, remove all current MediaData objects and inform listeners
        val listenersCopy = listeners.toSet()
        val listenersCopy = listeners
        val keyCopy = mediaEntries.keys.toMutableList()
        val keyCopy = userEntries.keys.toMutableList()
        // Clear the list first, to make sure callbacks from listeners if we have any entries
        // Clear the list first, to make sure callbacks from listeners if we have any entries
        // are up to date
        // are up to date
        mediaEntries.clear()
        userEntries.clear()
        keyCopy.forEach {
        keyCopy.forEach {
            if (DEBUG) Log.d(TAG, "Removing $it after user change")
            if (DEBUG) Log.d(TAG, "Removing $it after user change")
            listenersCopy.forEach { listener ->
            listenersCopy.forEach { listener ->
@@ -105,10 +107,10 @@ class MediaDataFilter @Inject constructor(
            }
            }
        }
        }


        dataSource.getData().forEach { (key, data) ->
        allEntries.forEach { (key, data) ->
            if (lockscreenUserManager.isCurrentProfile(data.userId)) {
            if (lockscreenUserManager.isCurrentProfile(data.userId)) {
                if (DEBUG) Log.d(TAG, "Re-adding $key after user change")
                if (DEBUG) Log.d(TAG, "Re-adding $key after user change")
                mediaEntries.put(key, data)
                userEntries.put(key, data)
                listenersCopy.forEach { listener ->
                listenersCopy.forEach { listener ->
                    listener.onMediaDataLoaded(key, null, data)
                    listener.onMediaDataLoaded(key, null, data)
                }
                }
@@ -121,7 +123,7 @@ class MediaDataFilter @Inject constructor(
     */
     */
    fun onSwipeToDismiss() {
    fun onSwipeToDismiss() {
        if (DEBUG) Log.d(TAG, "Media carousel swiped away")
        if (DEBUG) Log.d(TAG, "Media carousel swiped away")
        val mediaKeys = mediaEntries.keys.toSet()
        val mediaKeys = userEntries.keys.toSet()
        mediaKeys.forEach {
        mediaKeys.forEach {
            mediaDataManager.setTimedOut(it, timedOut = true)
            mediaDataManager.setTimedOut(it, timedOut = true)
        }
        }
@@ -130,7 +132,7 @@ class MediaDataFilter @Inject constructor(
    /**
    /**
     * Are there any media notifications active?
     * Are there any media notifications active?
     */
     */
    fun hasActiveMedia() = mediaEntries.any { it.value.active }
    fun hasActiveMedia() = userEntries.any { it.value.active }


    /**
    /**
     * Are there any media entries we should display?
     * Are there any media entries we should display?
@@ -138,7 +140,7 @@ class MediaDataFilter @Inject constructor(
     * If resumption is disabled, we only want to show active players
     * If resumption is disabled, we only want to show active players
     */
     */
    fun hasAnyMedia() = if (mediaResumeListener.isResumptionEnabled()) {
    fun hasAnyMedia() = if (mediaResumeListener.isResumptionEnabled()) {
        mediaEntries.isNotEmpty()
        userEntries.isNotEmpty()
    } else {
    } else {
        hasActiveMedia()
        hasActiveMedia()
    }
    }
@@ -146,10 +148,10 @@ class MediaDataFilter @Inject constructor(
    /**
    /**
     * Add a listener for filtered [MediaData] changes
     * Add a listener for filtered [MediaData] changes
     */
     */
    fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
    fun addListener(listener: MediaDataManager.Listener) = _listeners.add(listener)


    /**
    /**
     * Remove a listener that was registered with addListener
     * Remove a listener that was registered with addListener
     */
     */
    fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
    fun removeListener(listener: MediaDataManager.Listener) = _listeners.remove(listener)
}
}
+99 −38
Original line number Original line Diff line number Diff line
@@ -101,12 +101,23 @@ class MediaDataManager(
    dumpManager: DumpManager,
    dumpManager: DumpManager,
    mediaTimeoutListener: MediaTimeoutListener,
    mediaTimeoutListener: MediaTimeoutListener,
    mediaResumeListener: MediaResumeListener,
    mediaResumeListener: MediaResumeListener,
    mediaSessionBasedFilter: MediaSessionBasedFilter,
    mediaDeviceManager: MediaDeviceManager,
    mediaDataCombineLatest: MediaDataCombineLatest,
    private val mediaDataFilter: MediaDataFilter,
    private val activityStarter: ActivityStarter,
    private val activityStarter: ActivityStarter,
    private var useMediaResumption: Boolean,
    private var useMediaResumption: Boolean,
    private val useQsMediaPlayer: Boolean
    private val useQsMediaPlayer: Boolean
) : Dumpable {
) : Dumpable {


    private val listeners: MutableSet<Listener> = mutableSetOf()
    // Internal listeners are part of the internal pipeline. External listeners (those registered
    // with [MediaDeviceManager.addListener]) receive events after they have propagated through
    // the internal pipeline.
    // Another way to think of the distinction between internal and external listeners is the
    // following. Internal listeners are listeners that MediaDataManager depends on, and external
    // listeners are listeners that depend on MediaDataManager.
    // TODO(b/159539991#comment5): Move internal listeners to separate package.
    private val internalListeners: MutableSet<Listener> = mutableSetOf()
    private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
    private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
    internal var appsBlockedFromResume: MutableSet<String> = Utils.getBlockedMediaApps(context)
    internal var appsBlockedFromResume: MutableSet<String> = Utils.getBlockedMediaApps(context)
        set(value) {
        set(value) {
@@ -130,9 +141,14 @@ class MediaDataManager(
        broadcastDispatcher: BroadcastDispatcher,
        broadcastDispatcher: BroadcastDispatcher,
        mediaTimeoutListener: MediaTimeoutListener,
        mediaTimeoutListener: MediaTimeoutListener,
        mediaResumeListener: MediaResumeListener,
        mediaResumeListener: MediaResumeListener,
        mediaSessionBasedFilter: MediaSessionBasedFilter,
        mediaDeviceManager: MediaDeviceManager,
        mediaDataCombineLatest: MediaDataCombineLatest,
        mediaDataFilter: MediaDataFilter,
        activityStarter: ActivityStarter
        activityStarter: ActivityStarter
    ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
    ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
            broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
            broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
            mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
            activityStarter, Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
            activityStarter, Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))


    private val appChangeReceiver = object : BroadcastReceiver() {
    private val appChangeReceiver = object : BroadcastReceiver() {
@@ -155,12 +171,26 @@ class MediaDataManager(


    init {
    init {
        dumpManager.registerDumpable(TAG, this)
        dumpManager.registerDumpable(TAG, this)

        // Initialize the internal processing pipeline. The listeners at the front of the pipeline
        // are set as internal listeners so that they receive events. From there, events are
        // propagated through the pipeline. The end of the pipeline is currently mediaDataFilter,
        // so it is responsible for dispatching events to external listeners. To achieve this,
        // external listeners that are registered with [MediaDataManager.addListener] are actually
        // registered as listeners to mediaDataFilter.
        addInternalListener(mediaTimeoutListener)
        addInternalListener(mediaResumeListener)
        addInternalListener(mediaSessionBasedFilter)
        mediaSessionBasedFilter.addListener(mediaDeviceManager)
        mediaSessionBasedFilter.addListener(mediaDataCombineLatest)
        mediaDeviceManager.addListener(mediaDataCombineLatest)
        mediaDataCombineLatest.addListener(mediaDataFilter)

        // Set up links back into the pipeline for listeners that need to send events upstream.
        mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
        mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
            setTimedOut(token, timedOut) }
            setTimedOut(token, timedOut) }
        addListener(mediaTimeoutListener)

        mediaResumeListener.setManager(this)
        mediaResumeListener.setManager(this)
        addListener(mediaResumeListener)
        mediaDataFilter.mediaDataManager = this


        val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED)
        val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED)
        broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL)
        broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL)
@@ -198,10 +228,9 @@ class MediaDataManager(


    private fun removeAllForPackage(packageName: String) {
    private fun removeAllForPackage(packageName: String) {
        Assert.isMainThread()
        Assert.isMainThread()
        val listenersCopy = listeners.toSet()
        val toRemove = mediaEntries.filter { it.value.packageName == packageName }
        val toRemove = mediaEntries.filter { it.value.packageName == packageName }
        toRemove.forEach {
        toRemove.forEach {
            removeEntry(it.key, listenersCopy)
            removeEntry(it.key)
        }
        }
    }
    }


@@ -261,12 +290,45 @@ class MediaDataManager(
    /**
    /**
     * Add a listener for changes in this class
     * Add a listener for changes in this class
     */
     */
    fun addListener(listener: Listener) = listeners.add(listener)
    fun addListener(listener: Listener) {
        // mediaDataFilter is the current end of the internal pipeline. Register external
        // listeners as listeners to it.
        mediaDataFilter.addListener(listener)
    }


    /**
    /**
     * Remove a listener for changes in this class
     * Remove a listener for changes in this class
     */
     */
    fun removeListener(listener: Listener) = listeners.remove(listener)
    fun removeListener(listener: Listener) {
        // Since mediaDataFilter is the current end of the internal pipelie, external listeners
        // have been registered to it. So, they need to be removed from it too.
        mediaDataFilter.removeListener(listener)
    }

    /**
     * Add a listener for internal events.
     */
    private fun addInternalListener(listener: Listener) = internalListeners.add(listener)

    /**
     * Notify internal listeners of loaded event.
     *
     * External listeners registered with [addListener] will be notified after the event propagates
     * through the internal listener pipeline.
     */
    private fun notifyMediaDataLoaded(key: String, oldKey: String?, info: MediaData) {
        internalListeners.forEach { it.onMediaDataLoaded(key, oldKey, info) }
    }

    /**
     * Notify internal listeners of removed event.
     *
     * 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) }
    }


    /**
    /**
     * Called whenever the player has been paused or stopped for a while, or swiped from QQS.
     * Called whenever the player has been paused or stopped for a while, or swiped from QQS.
@@ -284,16 +346,13 @@ class MediaDataManager(
        }
        }
    }
    }


    private fun removeEntry(key: String, listenersCopy: Set<Listener>) {
    private fun removeEntry(key: String) {
        mediaEntries.remove(key)
        mediaEntries.remove(key)
        listenersCopy.forEach {
        notifyMediaDataRemoved(key)
            it.onMediaDataRemoved(key)
        }
    }
    }


    fun dismissMediaData(key: String, delay: Long) {
    fun dismissMediaData(key: String, delay: Long) {
        val listenersCopy = listeners.toSet()
        foregroundExecutor.executeDelayed({ removeEntry(key) }, delay)
        foregroundExecutor.executeDelayed({ removeEntry(key, listenersCopy) }, delay)
    }
    }


    private fun loadMediaDataInBgForResumption(
    private fun loadMediaDataInBgForResumption(
@@ -550,10 +609,7 @@ class MediaDataManager(
        if (mediaEntries.containsKey(key)) {
        if (mediaEntries.containsKey(key)) {
            // Otherwise this was removed already
            // Otherwise this was removed already
            mediaEntries.put(key, data)
            mediaEntries.put(key, data)
            val listenersCopy = listeners.toSet()
            notifyMediaDataLoaded(key, oldKey, data)
            listenersCopy.forEach {
                it.onMediaDataLoaded(key, oldKey, data)
            }
        }
        }
    }
    }


@@ -570,31 +626,21 @@ class MediaDataManager(
            val pkg = removed?.packageName
            val pkg = removed?.packageName
            val migrate = mediaEntries.put(pkg, updated) == null
            val migrate = mediaEntries.put(pkg, updated) == null
            // Notify listeners of "new" controls when migrating or removed and update when not
            // Notify listeners of "new" controls when migrating or removed and update when not
            val listenersCopy = listeners.toSet()
            if (migrate) {
            if (migrate) {
                listenersCopy.forEach {
                notifyMediaDataLoaded(pkg, key, updated)
                    it.onMediaDataLoaded(pkg, key, updated)
                }
            } else {
            } else {
                // Since packageName is used for the key of the resumption controls, it is
                // Since packageName is used for the key of the resumption controls, it is
                // possible that another notification has already been reused for the resumption
                // possible that another notification has already been reused for the resumption
                // controls of this package. In this case, rather than renaming this player as
                // controls of this package. In this case, rather than renaming this player as
                // packageName, just remove it and then send a update to the existing resumption
                // packageName, just remove it and then send a update to the existing resumption
                // controls.
                // controls.
                listenersCopy.forEach {
                notifyMediaDataRemoved(key)
                    it.onMediaDataRemoved(key)
                notifyMediaDataLoaded(pkg, pkg, updated)
                }
                listenersCopy.forEach {
                    it.onMediaDataLoaded(pkg, pkg, updated)
                }
            }
            }
            return
            return
        }
        }
        if (removed != null) {
        if (removed != null) {
            val listenersCopy = listeners.toSet()
            notifyMediaDataRemoved(key)
            listenersCopy.forEach {
                it.onMediaDataRemoved(key)
            }
        }
        }
    }
    }


@@ -614,17 +660,31 @@ class MediaDataManager(


        if (!useMediaResumption) {
        if (!useMediaResumption) {
            // Remove any existing resume controls
            // Remove any existing resume controls
            val listenersCopy = listeners.toSet()
            val filtered = mediaEntries.filter { !it.value.active }
            val filtered = mediaEntries.filter { !it.value.active }
            filtered.forEach {
            filtered.forEach {
                mediaEntries.remove(it.key)
                mediaEntries.remove(it.key)
                listenersCopy.forEach { listener ->
                notifyMediaDataRemoved(it.key)
                    listener.onMediaDataRemoved(it.key)
                }
            }
            }
        }
        }
    }
    }


    /**
     * Invoked when the user has dismissed the media carousel
     */
    fun onSwipeToDismiss() = mediaDataFilter.onSwipeToDismiss()

    /**
     * Are there any media notifications active?
     */
    fun hasActiveMedia() = mediaDataFilter.hasActiveMedia()

    /**
     * Are there any media entries we should display?
     * If resumption is enabled, this will include inactive players
     * If resumption is disabled, we only want to show active players
     */
    fun hasAnyMedia() = mediaDataFilter.hasAnyMedia()

    interface Listener {
    interface Listener {


        /**
        /**
@@ -644,7 +704,8 @@ class MediaDataManager(


    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
        pw.apply {
        pw.apply {
            println("listeners: $listeners")
            println("internalListeners: $internalListeners")
            println("externalListeners: ${mediaDataFilter.listeners}")
            println("mediaEntries: $mediaEntries")
            println("mediaEntries: $mediaEntries")
            println("useMediaResumption: $useMediaResumption")
            println("useMediaResumption: $useMediaResumption")
            println("appsBlockedFromResume: $appsBlockedFromResume")
            println("appsBlockedFromResume: $appsBlockedFromResume")
Loading