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 Diff line number Diff line
@@ -43,6 +43,7 @@ import android.hardware.display.ColorDisplayManager;
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.media.MediaRouter2Manager;
import android.media.session.MediaSessionManager;
import android.net.ConnectivityManager;
import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
@@ -225,6 +226,11 @@ public class SystemServicesModule {
        return MediaRouter2Manager.getInstance(context);
    }

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

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

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(
    private val dataSource: MediaDataManager,
    private val deviceSource: MediaDeviceManager
) {
class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
        MediaDeviceManager.Listener {

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

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

    override fun onMediaDataRemoved(key: String) {
        remove(key)
    }
        })
        deviceSource.addListener(object : MediaDeviceManager.Listener {

    override fun onMediaDeviceChanged(
        key: String,
        oldKey: String?,
@@ -59,22 +54,10 @@ class MediaDataCombineLatest @Inject constructor(
            update(key, key)
        }
    }

    override fun onKeyRemoved(key: String) {
        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].
+32 −30
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import com.android.systemui.settings.CurrentUserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Singleton

private const val TAG = "MediaDataFilter"
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
 * 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
 * background users (e.g. timeouts) that UI classes should ignore.
 * Instead, UI classes should listen to this so they can stay in sync with the current user.
 * This is added at the end of the pipeline since we may still need to handle callbacks from
 * background users (e.g. timeouts).
 */
@Singleton
class MediaDataFilter @Inject constructor(
    private val dataSource: MediaDataCombineLatest,
    private val broadcastDispatcher: BroadcastDispatcher,
    private val mediaResumeListener: MediaResumeListener,
    private val mediaDataManager: MediaDataManager,
    private val lockscreenUserManager: NotificationLockscreenUserManager,
    @Main private val executor: Executor
) : MediaDataManager.Listener {
    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 mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
    private val allEntries: 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 {
        userTracker = object : CurrentUserTracker(broadcastDispatcher) {
@@ -60,31 +59,34 @@ class MediaDataFilter @Inject constructor(
            }
        }
        userTracker.startTracking()
        dataSource.addListener(this)
    }

    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)) {
            return
        }

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

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

    override fun onMediaDataRemoved(key: String) {
        mediaEntries.remove(key)?.let {
        allEntries.remove(key)
        userEntries.remove(key)?.let {
            // Only notify listeners if something actually changed
            val listenersCopy = listeners.toSet()
            listenersCopy.forEach {
            listeners.forEach {
                it.onMediaDataRemoved(key)
            }
        }
@@ -93,11 +95,11 @@ class MediaDataFilter @Inject constructor(
    @VisibleForTesting
    internal fun handleUserSwitched(id: Int) {
        // If the user changes, remove all current MediaData objects and inform listeners
        val listenersCopy = listeners.toSet()
        val keyCopy = mediaEntries.keys.toMutableList()
        val listenersCopy = listeners
        val keyCopy = userEntries.keys.toMutableList()
        // Clear the list first, to make sure callbacks from listeners if we have any entries
        // are up to date
        mediaEntries.clear()
        userEntries.clear()
        keyCopy.forEach {
            if (DEBUG) Log.d(TAG, "Removing $it after user change")
            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 (DEBUG) Log.d(TAG, "Re-adding $key after user change")
                mediaEntries.put(key, data)
                userEntries.put(key, data)
                listenersCopy.forEach { listener ->
                    listener.onMediaDataLoaded(key, null, data)
                }
@@ -121,7 +123,7 @@ class MediaDataFilter @Inject constructor(
     */
    fun onSwipeToDismiss() {
        if (DEBUG) Log.d(TAG, "Media carousel swiped away")
        val mediaKeys = mediaEntries.keys.toSet()
        val mediaKeys = userEntries.keys.toSet()
        mediaKeys.forEach {
            mediaDataManager.setTimedOut(it, timedOut = true)
        }
@@ -130,7 +132,7 @@ class MediaDataFilter @Inject constructor(
    /**
     * 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?
@@ -138,7 +140,7 @@ class MediaDataFilter @Inject constructor(
     * If resumption is disabled, we only want to show active players
     */
    fun hasAnyMedia() = if (mediaResumeListener.isResumptionEnabled()) {
        mediaEntries.isNotEmpty()
        userEntries.isNotEmpty()
    } else {
        hasActiveMedia()
    }
@@ -146,10 +148,10 @@ class MediaDataFilter @Inject constructor(
    /**
     * 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
     */
    fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
    fun removeListener(listener: MediaDataManager.Listener) = _listeners.remove(listener)
}
+99 −38
Original line number Diff line number Diff line
@@ -101,12 +101,23 @@ class MediaDataManager(
    dumpManager: DumpManager,
    mediaTimeoutListener: MediaTimeoutListener,
    mediaResumeListener: MediaResumeListener,
    mediaSessionBasedFilter: MediaSessionBasedFilter,
    mediaDeviceManager: MediaDeviceManager,
    mediaDataCombineLatest: MediaDataCombineLatest,
    private val mediaDataFilter: MediaDataFilter,
    private val activityStarter: ActivityStarter,
    private var useMediaResumption: Boolean,
    private val useQsMediaPlayer: Boolean
) : 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()
    internal var appsBlockedFromResume: MutableSet<String> = Utils.getBlockedMediaApps(context)
        set(value) {
@@ -130,9 +141,14 @@ class MediaDataManager(
        broadcastDispatcher: BroadcastDispatcher,
        mediaTimeoutListener: MediaTimeoutListener,
        mediaResumeListener: MediaResumeListener,
        mediaSessionBasedFilter: MediaSessionBasedFilter,
        mediaDeviceManager: MediaDeviceManager,
        mediaDataCombineLatest: MediaDataCombineLatest,
        mediaDataFilter: MediaDataFilter,
        activityStarter: ActivityStarter
    ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
            broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
            mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
            activityStarter, Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))

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

    init {
        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 ->
            setTimedOut(token, timedOut) }
        addListener(mediaTimeoutListener)

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

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

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

@@ -261,12 +290,45 @@ class MediaDataManager(
    /**
     * 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
     */
    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.
@@ -284,16 +346,13 @@ class MediaDataManager(
        }
    }

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

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

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

@@ -570,31 +626,21 @@ class MediaDataManager(
            val pkg = removed?.packageName
            val migrate = mediaEntries.put(pkg, updated) == null
            // Notify listeners of "new" controls when migrating or removed and update when not
            val listenersCopy = listeners.toSet()
            if (migrate) {
                listenersCopy.forEach {
                    it.onMediaDataLoaded(pkg, key, updated)
                }
                notifyMediaDataLoaded(pkg, key, updated)
            } else {
                // Since packageName is used for the key of the resumption controls, it is
                // possible that another notification has already been reused for the resumption
                // 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
                // controls.
                listenersCopy.forEach {
                    it.onMediaDataRemoved(key)
                }
                listenersCopy.forEach {
                    it.onMediaDataLoaded(pkg, pkg, updated)
                }
                notifyMediaDataRemoved(key)
                notifyMediaDataLoaded(pkg, pkg, updated)
            }
            return
        }
        if (removed != null) {
            val listenersCopy = listeners.toSet()
            listenersCopy.forEach {
                it.onMediaDataRemoved(key)
            }
            notifyMediaDataRemoved(key)
        }
    }

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

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

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

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