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