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 }