1 /* <lambda>null2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.media 18 19 import android.util.Log 20 import com.android.internal.annotations.VisibleForTesting 21 import com.android.systemui.broadcast.BroadcastDispatcher 22 import com.android.systemui.dagger.qualifiers.Main 23 import com.android.systemui.settings.CurrentUserTracker 24 import com.android.systemui.statusbar.NotificationLockscreenUserManager 25 import java.util.concurrent.Executor 26 import javax.inject.Inject 27 import javax.inject.Singleton 28 29 private const val TAG = "MediaDataFilter" 30 31 /** 32 * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user 33 * switches (removing entries for the previous user, adding back entries for the current user) 34 * 35 * This is added downstream of [MediaDataManager] since we may still need to handle callbacks from 36 * background users (e.g. timeouts) that UI classes should ignore. 37 * Instead, UI classes should listen to this so they can stay in sync with the current user. 38 */ 39 @Singleton 40 class MediaDataFilter @Inject constructor( 41 private val dataSource: MediaDataCombineLatest, 42 private val broadcastDispatcher: BroadcastDispatcher, 43 private val mediaResumeListener: MediaResumeListener, 44 private val mediaDataManager: MediaDataManager, 45 private val lockscreenUserManager: NotificationLockscreenUserManager, 46 @Main private val executor: Executor 47 ) : MediaDataManager.Listener { 48 private val userTracker: CurrentUserTracker 49 private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf() 50 51 // The filtered mediaEntries, which will be a subset of all mediaEntries in MediaDataManager 52 private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap() 53 54 init { 55 userTracker = object : CurrentUserTracker(broadcastDispatcher) { 56 override fun onUserSwitched(newUserId: Int) { 57 // Post this so we can be sure lockscreenUserManager already got the broadcast 58 executor.execute { handleUserSwitched(newUserId) } 59 } 60 } 61 userTracker.startTracking() 62 dataSource.addListener(this) 63 } 64 65 override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { 66 if (!lockscreenUserManager.isCurrentProfile(data.userId)) { 67 return 68 } 69 70 if (oldKey != null) { 71 mediaEntries.remove(oldKey) 72 } 73 mediaEntries.put(key, data) 74 75 // Notify listeners 76 val listenersCopy = listeners.toSet() 77 listenersCopy.forEach { 78 it.onMediaDataLoaded(key, oldKey, data) 79 } 80 } 81 82 override fun onMediaDataRemoved(key: String) { 83 mediaEntries.remove(key)?.let { 84 // Only notify listeners if something actually changed 85 val listenersCopy = listeners.toSet() 86 listenersCopy.forEach { 87 it.onMediaDataRemoved(key) 88 } 89 } 90 } 91 92 @VisibleForTesting 93 internal fun handleUserSwitched(id: Int) { 94 // If the user changes, remove all current MediaData objects and inform listeners 95 val listenersCopy = listeners.toSet() 96 val keyCopy = mediaEntries.keys.toMutableList() 97 // Clear the list first, to make sure callbacks from listeners if we have any entries 98 // are up to date 99 mediaEntries.clear() 100 keyCopy.forEach { 101 Log.d(TAG, "Removing $it after user change") 102 listenersCopy.forEach { listener -> 103 listener.onMediaDataRemoved(it) 104 } 105 } 106 107 dataSource.getData().forEach { (key, data) -> 108 if (lockscreenUserManager.isCurrentProfile(data.userId)) { 109 Log.d(TAG, "Re-adding $key after user change") 110 mediaEntries.put(key, data) 111 listenersCopy.forEach { listener -> 112 listener.onMediaDataLoaded(key, null, data) 113 } 114 } 115 } 116 } 117 118 /** 119 * Invoked when the user has dismissed the media carousel 120 */ 121 fun onSwipeToDismiss() { 122 val mediaKeys = mediaEntries.keys.toSet() 123 mediaKeys.forEach { 124 mediaDataManager.setTimedOut(it, timedOut = true) 125 } 126 } 127 128 /** 129 * Are there any media notifications active? 130 */ 131 fun hasActiveMedia() = mediaEntries.any { it.value.active } 132 133 /** 134 * Are there any media entries we should display? 135 * If resumption is enabled, this will include inactive players 136 * If resumption is disabled, we only want to show active players 137 */ 138 fun hasAnyMedia() = if (mediaResumeListener.isResumptionEnabled()) { 139 mediaEntries.isNotEmpty() 140 } else { 141 hasActiveMedia() 142 } 143 144 /** 145 * Add a listener for filtered [MediaData] changes 146 */ 147 fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener) 148 149 /** 150 * Remove a listener that was registered with addListener 151 */ 152 fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener) 153 }