1 /*
2  * 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.controls.domain.pipeline
18 
19 import android.media.session.MediaController
20 import android.media.session.MediaController.PlaybackInfo
21 import android.media.session.MediaSession
22 import android.media.session.MediaSessionManager
23 import android.testing.TestableLooper
24 import androidx.test.ext.junit.runners.AndroidJUnit4
25 import androidx.test.filters.SmallTest
26 import com.android.systemui.SysuiTestCase
27 import com.android.systemui.media.controls.MediaTestUtils
28 import com.android.systemui.media.controls.shared.model.MediaData
29 import com.android.systemui.util.concurrency.FakeExecutor
30 import com.android.systemui.util.time.FakeSystemClock
31 import org.junit.After
32 import org.junit.Before
33 import org.junit.Rule
34 import org.junit.Test
35 import org.junit.runner.RunWith
36 import org.mockito.ArgumentCaptor
37 import org.mockito.ArgumentMatchers.anyBoolean
38 import org.mockito.ArgumentMatchers.anyInt
39 import org.mockito.Mock
40 import org.mockito.Mockito.never
41 import org.mockito.Mockito.reset
42 import org.mockito.Mockito.verify
43 import org.mockito.Mockito.`when` as whenever
44 import org.mockito.junit.MockitoJUnit
45 import org.mockito.kotlin.any
46 import org.mockito.kotlin.eq
47 
48 private const val PACKAGE = "PKG"
49 private const val KEY = "TEST_KEY"
50 private const val NOTIF_KEY = "TEST_KEY"
51 
52 private val info =
53     MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, notificationKey = NOTIF_KEY)
54 
55 @SmallTest
56 @RunWith(AndroidJUnit4::class)
57 @TestableLooper.RunWithLooper
58 public class MediaSessionBasedFilterTest : SysuiTestCase() {
59 
60     @JvmField @Rule val mockito = MockitoJUnit.rule()
61 
62     // Unit to be tested
63     private lateinit var filter: MediaSessionBasedFilter
64 
65     private lateinit var sessionListener: MediaSessionManager.OnActiveSessionsChangedListener
66     @Mock private lateinit var mediaListener: MediaDataManager.Listener
67 
68     // MediaSessionBasedFilter dependencies
69     @Mock private lateinit var mediaSessionManager: MediaSessionManager
70     private lateinit var fgExecutor: FakeExecutor
71     private lateinit var bgExecutor: FakeExecutor
72 
73     @Mock private lateinit var controller1: MediaController
74     @Mock private lateinit var controller2: MediaController
75     @Mock private lateinit var controller3: MediaController
76     @Mock private lateinit var controller4: MediaController
77 
78     private lateinit var token1: MediaSession.Token
79     private lateinit var token2: MediaSession.Token
80     private lateinit var token3: MediaSession.Token
81     private lateinit var token4: MediaSession.Token
82 
83     @Mock private lateinit var remotePlaybackInfo: PlaybackInfo
84     @Mock private lateinit var localPlaybackInfo: PlaybackInfo
85 
86     private lateinit var session1: MediaSession
87     private lateinit var session2: MediaSession
88     private lateinit var session3: MediaSession
89     private lateinit var session4: MediaSession
90 
91     private lateinit var mediaData1: MediaData
92     private lateinit var mediaData2: MediaData
93     private lateinit var mediaData3: MediaData
94     private lateinit var mediaData4: MediaData
95 
96     @Before
setUpnull97     fun setUp() {
98         fgExecutor = FakeExecutor(FakeSystemClock())
99         bgExecutor = FakeExecutor(FakeSystemClock())
100         filter = MediaSessionBasedFilter(context, mediaSessionManager, fgExecutor, bgExecutor)
101 
102         // Configure mocks.
103         whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(emptyList())
104 
105         session1 = MediaSession(context, "MediaSessionBasedFilter1")
106         session2 = MediaSession(context, "MediaSessionBasedFilter2")
107         session3 = MediaSession(context, "MediaSessionBasedFilter3")
108         session4 = MediaSession(context, "MediaSessionBasedFilter4")
109 
110         token1 = session1.sessionToken
111         token2 = session2.sessionToken
112         token3 = session3.sessionToken
113         token4 = session4.sessionToken
114 
115         whenever(controller1.getSessionToken()).thenReturn(token1)
116         whenever(controller2.getSessionToken()).thenReturn(token2)
117         whenever(controller3.getSessionToken()).thenReturn(token3)
118         whenever(controller4.getSessionToken()).thenReturn(token4)
119 
120         whenever(controller1.getPackageName()).thenReturn(PACKAGE)
121         whenever(controller2.getPackageName()).thenReturn(PACKAGE)
122         whenever(controller3.getPackageName()).thenReturn(PACKAGE)
123         whenever(controller4.getPackageName()).thenReturn(PACKAGE)
124 
125         mediaData1 = info.copy(token = token1)
126         mediaData2 = info.copy(token = token2)
127         mediaData3 = info.copy(token = token3)
128         mediaData4 = info.copy(token = token4)
129 
130         whenever(remotePlaybackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
131         whenever(localPlaybackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
132 
133         whenever(controller1.getPlaybackInfo()).thenReturn(localPlaybackInfo)
134         whenever(controller2.getPlaybackInfo()).thenReturn(localPlaybackInfo)
135         whenever(controller3.getPlaybackInfo()).thenReturn(localPlaybackInfo)
136         whenever(controller4.getPlaybackInfo()).thenReturn(localPlaybackInfo)
137 
138         // Capture listener
139         bgExecutor.runAllReady()
140         val listenerCaptor =
141             ArgumentCaptor.forClass(MediaSessionManager.OnActiveSessionsChangedListener::class.java)
142         verify(mediaSessionManager)
143             .addOnActiveSessionsChangedListener(listenerCaptor.capture(), any())
144         sessionListener = listenerCaptor.value
145 
146         filter.addListener(mediaListener)
147     }
148 
149     @After
tearDownnull150     fun tearDown() {
151         session1.release()
152         session2.release()
153         session3.release()
154         session4.release()
155     }
156 
157     @Test
noMediaSession_loadedEventNotFilterednull158     fun noMediaSession_loadedEventNotFiltered() {
159         filter.onMediaDataLoaded(KEY, null, mediaData1)
160         bgExecutor.runAllReady()
161         fgExecutor.runAllReady()
162         verify(mediaListener)
163             .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
164     }
165 
166     @Test
noMediaSession_removedEventNotFilterednull167     fun noMediaSession_removedEventNotFiltered() {
168         filter.onMediaDataRemoved(KEY, false)
169         bgExecutor.runAllReady()
170         fgExecutor.runAllReady()
171         verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false))
172     }
173 
174     @Test
matchingMediaSession_loadedEventNotFilterednull175     fun matchingMediaSession_loadedEventNotFiltered() {
176         // GIVEN an active session
177         val controllers = listOf(controller1)
178         whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
179         sessionListener.onActiveSessionsChanged(controllers)
180         // WHEN a loaded event is received that matches the session
181         filter.onMediaDataLoaded(KEY, null, mediaData1)
182         bgExecutor.runAllReady()
183         fgExecutor.runAllReady()
184         // THEN the event is not filtered
185         verify(mediaListener)
186             .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
187     }
188 
189     @Test
matchingMediaSession_removedEventNotFilterednull190     fun matchingMediaSession_removedEventNotFiltered() {
191         // GIVEN an active session
192         val controllers = listOf(controller1)
193         whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
194         sessionListener.onActiveSessionsChanged(controllers)
195         // WHEN a removed event is received
196         filter.onMediaDataRemoved(KEY, false)
197         bgExecutor.runAllReady()
198         fgExecutor.runAllReady()
199         // THEN the event is not filtered
200         verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false))
201     }
202 
203     @Test
remoteSession_loadedEventNotFilterednull204     fun remoteSession_loadedEventNotFiltered() {
205         // GIVEN a remote session
206         whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
207         val controllers = listOf(controller1)
208         whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
209         sessionListener.onActiveSessionsChanged(controllers)
210         // WHEN a loaded event is received that matche the session
211         filter.onMediaDataLoaded(KEY, null, mediaData1)
212         bgExecutor.runAllReady()
213         fgExecutor.runAllReady()
214         // THEN the event is not filtered
215         verify(mediaListener)
216             .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
217     }
218 
219     @Test
remoteAndLocalSessions_localLoadedEventFilterednull220     fun remoteAndLocalSessions_localLoadedEventFiltered() {
221         // GIVEN remote and local sessions
222         whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
223         val controllers = listOf(controller1, controller2)
224         whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
225         sessionListener.onActiveSessionsChanged(controllers)
226         // WHEN a loaded event is received that matches the remote session
227         filter.onMediaDataLoaded(KEY, null, mediaData1)
228         bgExecutor.runAllReady()
229         fgExecutor.runAllReady()
230         // THEN the event is not filtered
231         verify(mediaListener)
232             .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
233         // WHEN a loaded event is received that matches the local session
234         filter.onMediaDataLoaded(KEY, null, mediaData2)
235         bgExecutor.runAllReady()
236         fgExecutor.runAllReady()
237         // THEN the event is filtered
238         verify(mediaListener, never())
239             .onMediaDataLoaded(
240                 eq(KEY),
241                 eq(null),
242                 eq(mediaData2),
243                 anyBoolean(),
244                 anyInt(),
245                 anyBoolean()
246             )
247     }
248 
249     @Test
remoteAndLocalSessions_remoteSessionWithoutNotificationnull250     fun remoteAndLocalSessions_remoteSessionWithoutNotification() {
251         // GIVEN remote and local sessions
252         whenever(controller2.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
253         val controllers = listOf(controller1, controller2)
254         whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
255         sessionListener.onActiveSessionsChanged(controllers)
256         // WHEN a loaded event is received that matches the local session
257         filter.onMediaDataLoaded(KEY, null, mediaData1)
258         bgExecutor.runAllReady()
259         fgExecutor.runAllReady()
260         // THEN the event is not filtered because there isn't a notification for the remote
261         // session.
262         verify(mediaListener)
263             .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
264     }
265 
266     @Test
remoteAndLocalHaveDifferentKeys_localLoadedEventFilterednull267     fun remoteAndLocalHaveDifferentKeys_localLoadedEventFiltered() {
268         // GIVEN remote and local sessions
269         val key1 = "KEY_1"
270         val key2 = "KEY_2"
271         whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
272         val controllers = listOf(controller1, controller2)
273         whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
274         sessionListener.onActiveSessionsChanged(controllers)
275         // WHEN a loaded event is received that matches the remote session
276         filter.onMediaDataLoaded(key1, null, mediaData1)
277         bgExecutor.runAllReady()
278         fgExecutor.runAllReady()
279         // THEN the event is not filtered
280         verify(mediaListener)
281             .onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
282         // WHEN a loaded event is received that matches the local session
283         filter.onMediaDataLoaded(key2, null, mediaData2)
284         bgExecutor.runAllReady()
285         fgExecutor.runAllReady()
286         // THEN the event is filtered
287         verify(mediaListener, never())
288             .onMediaDataLoaded(
289                 eq(key2),
290                 eq(null),
291                 eq(mediaData2),
292                 anyBoolean(),
293                 anyInt(),
294                 anyBoolean()
295             )
296         // AND there should be a removed event for key2
297         verify(mediaListener).onMediaDataRemoved(eq(key2), eq(false))
298     }
299 
300     @Test
remoteAndLocalHaveDifferentKeys_remoteSessionWithoutNotificationnull301     fun remoteAndLocalHaveDifferentKeys_remoteSessionWithoutNotification() {
302         // GIVEN remote and local sessions
303         val key1 = "KEY_1"
304         val key2 = "KEY_2"
305         whenever(controller2.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
306         val controllers = listOf(controller1, controller2)
307         whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
308         sessionListener.onActiveSessionsChanged(controllers)
309         // WHEN a loaded event is received that matches the local session
310         filter.onMediaDataLoaded(key1, null, mediaData1)
311         bgExecutor.runAllReady()
312         fgExecutor.runAllReady()
313         // THEN the event is not filtered
314         verify(mediaListener)
315             .onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
316         // WHEN a loaded event is received that matches the remote session
317         filter.onMediaDataLoaded(key2, null, mediaData2)
318         bgExecutor.runAllReady()
319         fgExecutor.runAllReady()
320         // THEN the event is not filtered
321         verify(mediaListener)
322             .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), eq(true), eq(0), eq(false))
323     }
324 
325     @Test
multipleRemoteSessions_loadedEventNotFilterednull326     fun multipleRemoteSessions_loadedEventNotFiltered() {
327         // GIVEN two remote sessions
328         whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
329         whenever(controller2.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
330         val controllers = listOf(controller1, controller2)
331         whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
332         sessionListener.onActiveSessionsChanged(controllers)
333         // WHEN a loaded event is received that matches the remote session
334         filter.onMediaDataLoaded(KEY, null, mediaData1)
335         bgExecutor.runAllReady()
336         fgExecutor.runAllReady()
337         // THEN the event is not filtered
338         verify(mediaListener)
339             .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
340         // WHEN a loaded event is received that matches the local session
341         filter.onMediaDataLoaded(KEY, null, mediaData2)
342         bgExecutor.runAllReady()
343         fgExecutor.runAllReady()
344         // THEN the event is not filtered
345         verify(mediaListener)
346             .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2), eq(true), eq(0), eq(false))
347     }
348 
349     @Test
multipleOtherSessions_loadedEventNotFilterednull350     fun multipleOtherSessions_loadedEventNotFiltered() {
351         // GIVEN multiple active sessions from other packages
352         val controllers = listOf(controller1, controller2, controller3, controller4)
353         whenever(controller1.getPackageName()).thenReturn("PKG_1")
354         whenever(controller2.getPackageName()).thenReturn("PKG_2")
355         whenever(controller3.getPackageName()).thenReturn("PKG_3")
356         whenever(controller4.getPackageName()).thenReturn("PKG_4")
357         whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
358         sessionListener.onActiveSessionsChanged(controllers)
359         // WHEN a loaded event is received
360         filter.onMediaDataLoaded(KEY, null, mediaData1)
361         bgExecutor.runAllReady()
362         fgExecutor.runAllReady()
363         // THEN the event is not filtered
364         verify(mediaListener)
365             .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
366     }
367 
368     @Test
doNotFilterDuringKeyMigrationnull369     fun doNotFilterDuringKeyMigration() {
370         val key1 = "KEY_1"
371         val key2 = "KEY_2"
372         // GIVEN a loaded event
373         filter.onMediaDataLoaded(key1, null, mediaData2)
374         bgExecutor.runAllReady()
375         fgExecutor.runAllReady()
376         reset(mediaListener)
377         // GIVEN remote and local sessions
378         whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
379         val controllers = listOf(controller1, controller2)
380         whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
381         sessionListener.onActiveSessionsChanged(controllers)
382         // WHEN a loaded event is received that matches the local session but it is a key migration
383         filter.onMediaDataLoaded(key2, key1, mediaData2)
384         bgExecutor.runAllReady()
385         fgExecutor.runAllReady()
386         // THEN the key migration event is fired
387         verify(mediaListener)
388             .onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2), eq(true), eq(0), eq(false))
389     }
390 
391     @Test
filterAfterKeyMigrationnull392     fun filterAfterKeyMigration() {
393         val key1 = "KEY_1"
394         val key2 = "KEY_2"
395         // GIVEN a loaded event
396         filter.onMediaDataLoaded(key1, null, mediaData1)
397         filter.onMediaDataLoaded(key1, null, mediaData2)
398         bgExecutor.runAllReady()
399         fgExecutor.runAllReady()
400         reset(mediaListener)
401         // GIVEN remote and local sessions
402         whenever(controller1.getPlaybackInfo()).thenReturn(remotePlaybackInfo)
403         val controllers = listOf(controller1, controller2)
404         whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers)
405         sessionListener.onActiveSessionsChanged(controllers)
406         // GIVEN that the keys have been migrated
407         filter.onMediaDataLoaded(key2, key1, mediaData1)
408         filter.onMediaDataLoaded(key2, key1, mediaData2)
409         bgExecutor.runAllReady()
410         fgExecutor.runAllReady()
411         reset(mediaListener)
412         // WHEN a loaded event is received that matches the local session
413         filter.onMediaDataLoaded(key2, null, mediaData2)
414         bgExecutor.runAllReady()
415         fgExecutor.runAllReady()
416         // THEN the key migration event is filtered
417         verify(mediaListener, never())
418             .onMediaDataLoaded(
419                 eq(key2),
420                 eq(null),
421                 eq(mediaData2),
422                 anyBoolean(),
423                 anyInt(),
424                 anyBoolean()
425             )
426         // WHEN a loaded event is received that matches the remote session
427         filter.onMediaDataLoaded(key2, null, mediaData1)
428         bgExecutor.runAllReady()
429         fgExecutor.runAllReady()
430         // THEN the key migration event is fired
431         verify(mediaListener)
432             .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
433     }
434 }
435