Loading packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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 ) { Loading packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt +31 −48 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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?, Loading @@ -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]. Loading packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +32 −30 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) { Loading @@ -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) } } Loading @@ -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 -> Loading @@ -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) } Loading @@ -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) } Loading @@ -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? Loading @@ -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() } Loading @@ -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) } packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +99 −38 Original line number Diff line number Diff line Loading @@ -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) { Loading @@ -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() { Loading @@ -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) Loading Loading @@ -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) } } Loading Loading @@ -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. Loading @@ -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( Loading Loading @@ -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) } } Loading @@ -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) } } Loading @@ -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 { /** Loading @@ -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 Loading
packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading
packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +1 −1 Original line number Diff line number Diff line Loading @@ -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 ) { Loading
packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt +31 −48 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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?, Loading @@ -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]. Loading
packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +32 −30 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) { Loading @@ -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) } } Loading @@ -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 -> Loading @@ -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) } Loading @@ -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) } Loading @@ -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? Loading @@ -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() } Loading @@ -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) }
packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +99 −38 Original line number Diff line number Diff line Loading @@ -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) { Loading @@ -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() { Loading @@ -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) Loading Loading @@ -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) } } Loading Loading @@ -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. Loading @@ -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( Loading Loading @@ -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) } } Loading @@ -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) } } Loading @@ -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 { /** Loading @@ -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