blob: 662831e4a445db8d4c68955fff9f8b882af4e521 [file] [log] [blame]
/*
* Copyright (C) 2020 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.media
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Main
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"
/**
* 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.
*/
@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()
// The filtered mediaEntries, which will be a subset of all mediaEntries in MediaDataManager
private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
init {
userTracker = object : CurrentUserTracker(broadcastDispatcher) {
override fun onUserSwitched(newUserId: Int) {
// Post this so we can be sure lockscreenUserManager already got the broadcast
executor.execute { handleUserSwitched(newUserId) }
}
}
userTracker.startTracking()
dataSource.addListener(this)
}
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
if (!lockscreenUserManager.isCurrentProfile(data.userId)) {
return
}
if (oldKey != null) {
mediaEntries.remove(oldKey)
}
mediaEntries.put(key, data)
// Notify listeners
val listenersCopy = listeners.toSet()
listenersCopy.forEach {
it.onMediaDataLoaded(key, oldKey, data)
}
}
override fun onMediaDataRemoved(key: String) {
mediaEntries.remove(key)?.let {
// Only notify listeners if something actually changed
val listenersCopy = listeners.toSet()
listenersCopy.forEach {
it.onMediaDataRemoved(key)
}
}
}
@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()
// Clear the list first, to make sure callbacks from listeners if we have any entries
// are up to date
mediaEntries.clear()
keyCopy.forEach {
Log.d(TAG, "Removing $it after user change")
listenersCopy.forEach { listener ->
listener.onMediaDataRemoved(it)
}
}
dataSource.getData().forEach { (key, data) ->
if (lockscreenUserManager.isCurrentProfile(data.userId)) {
Log.d(TAG, "Re-adding $key after user change")
mediaEntries.put(key, data)
listenersCopy.forEach { listener ->
listener.onMediaDataLoaded(key, null, data)
}
}
}
}
/**
* Invoked when the user has dismissed the media carousel
*/
fun onSwipeToDismiss() {
val mediaKeys = mediaEntries.keys.toSet()
mediaKeys.forEach {
mediaDataManager.setTimedOut(it, timedOut = true)
}
}
/**
* Are there any media notifications active?
*/
fun hasActiveMedia() = mediaEntries.any { it.value.active }
/**
* 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() = if (mediaResumeListener.isResumptionEnabled()) {
mediaEntries.isNotEmpty()
} else {
hasActiveMedia()
}
/**
* Add a listener for filtered [MediaData] changes
*/
fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
/**
* Remove a listener that was registered with addListener
*/
fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
}