1 /* 2 * Copyright (C) 2021 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.ui.controller 18 19 import android.app.PendingIntent 20 import android.content.res.ColorStateList 21 import android.content.res.Configuration 22 import android.database.ContentObserver 23 import android.os.LocaleList 24 import android.platform.test.flag.junit.FlagsParameterization 25 import android.provider.Settings 26 import android.testing.TestableLooper 27 import android.util.MathUtils.abs 28 import android.view.View 29 import androidx.test.filters.SmallTest 30 import com.android.compose.animation.scene.ObservableTransitionState 31 import com.android.compose.animation.scene.SceneKey 32 import com.android.internal.logging.InstanceId 33 import com.android.keyguard.KeyguardUpdateMonitor 34 import com.android.keyguard.KeyguardUpdateMonitorCallback 35 import com.android.systemui.Flags.mediaControlsUmoInflationInBackground 36 import com.android.systemui.SysuiTestCase 37 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor 38 import com.android.systemui.dump.DumpManager 39 import com.android.systemui.flags.DisableSceneContainer 40 import com.android.systemui.flags.EnableSceneContainer 41 import com.android.systemui.flags.Flags 42 import com.android.systemui.flags.fakeFeatureFlagsClassic 43 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository 44 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository 45 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor 46 import com.android.systemui.keyguard.shared.model.KeyguardState 47 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus 48 import com.android.systemui.kosmos.applicationCoroutineScope 49 import com.android.systemui.kosmos.testDispatcher 50 import com.android.systemui.kosmos.testScope 51 import com.android.systemui.kosmos.useUnconfinedTestDispatcher 52 import com.android.systemui.media.controls.MediaTestUtils 53 import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA 54 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager 55 import com.android.systemui.media.controls.shared.model.MediaData 56 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS 57 import com.android.systemui.media.controls.ui.view.MediaHostState 58 import com.android.systemui.media.controls.ui.view.MediaScrollView 59 import com.android.systemui.media.controls.ui.viewmodel.mediaCarouselViewModel 60 import com.android.systemui.media.controls.util.MediaFlags 61 import com.android.systemui.media.controls.util.MediaUiEventLogger 62 import com.android.systemui.plugins.ActivityStarter 63 import com.android.systemui.plugins.FalsingManager 64 import com.android.systemui.qs.PageIndicator 65 import com.android.systemui.res.R 66 import com.android.systemui.scene.data.repository.Idle 67 import com.android.systemui.scene.data.repository.setSceneTransition 68 import com.android.systemui.scene.domain.interactor.sceneInteractor 69 import com.android.systemui.scene.shared.model.Scenes 70 import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener 71 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider 72 import com.android.systemui.statusbar.policy.ConfigurationController 73 import com.android.systemui.testKosmos 74 import com.android.systemui.util.concurrency.FakeExecutor 75 import com.android.systemui.util.settings.GlobalSettings 76 import com.android.systemui.util.settings.fakeSettings 77 import com.android.systemui.util.time.FakeSystemClock 78 import com.google.common.truth.Truth.assertThat 79 import java.util.Locale 80 import javax.inject.Provider 81 import junit.framework.Assert.assertEquals 82 import junit.framework.Assert.assertFalse 83 import junit.framework.Assert.assertTrue 84 import kotlinx.coroutines.ExperimentalCoroutinesApi 85 import kotlinx.coroutines.flow.MutableStateFlow 86 import kotlinx.coroutines.test.TestScope 87 import kotlinx.coroutines.test.runCurrent 88 import kotlinx.coroutines.test.runTest 89 import org.junit.After 90 import org.junit.Before 91 import org.junit.Test 92 import org.junit.runner.RunWith 93 import org.mockito.ArgumentCaptor 94 import org.mockito.Captor 95 import org.mockito.Mock 96 import org.mockito.Mockito.anyLong 97 import org.mockito.Mockito.floatThat 98 import org.mockito.Mockito.mock 99 import org.mockito.Mockito.never 100 import org.mockito.Mockito.reset 101 import org.mockito.Mockito.times 102 import org.mockito.Mockito.verify 103 import org.mockito.Mockito.`when` as whenever 104 import org.mockito.MockitoAnnotations 105 import org.mockito.kotlin.any 106 import org.mockito.kotlin.capture 107 import org.mockito.kotlin.eq 108 import platform.test.runner.parameterized.ParameterizedAndroidJunit4 109 import platform.test.runner.parameterized.Parameters 110 111 private val DATA = MediaTestUtils.emptyMediaData 112 113 private val SMARTSPACE_KEY = "smartspace" 114 private const val PAUSED_LOCAL = "paused local" 115 private const val PLAYING_LOCAL = "playing local" 116 117 @ExperimentalCoroutinesApi 118 @SmallTest 119 @TestableLooper.RunWithLooper(setAsMainLooper = true) 120 @RunWith(ParameterizedAndroidJunit4::class) 121 class MediaCarouselControllerTest(flags: FlagsParameterization) : SysuiTestCase() { 122 private val kosmos = testKosmos().useUnconfinedTestDispatcher() 123 private val testDispatcher = kosmos.testDispatcher 124 private val secureSettings = kosmos.fakeSettings 125 126 @Mock lateinit var mediaControlPanelFactory: Provider<MediaControlPanel> 127 @Mock lateinit var mediaViewControllerFactory: Provider<MediaViewController> 128 @Mock lateinit var panel: MediaControlPanel 129 @Mock lateinit var visualStabilityProvider: VisualStabilityProvider 130 @Mock lateinit var mediaHostStatesManager: MediaHostStatesManager 131 @Mock lateinit var mediaHostState: MediaHostState 132 @Mock lateinit var activityStarter: ActivityStarter 133 @Mock lateinit var mediaDataManager: MediaDataManager 134 @Mock lateinit var configurationController: ConfigurationController 135 @Mock lateinit var falsingManager: FalsingManager 136 @Mock lateinit var dumpManager: DumpManager 137 @Mock lateinit var logger: MediaUiEventLogger 138 @Mock lateinit var debugLogger: MediaCarouselControllerLogger 139 @Mock lateinit var mediaViewController: MediaViewController 140 @Mock lateinit var mediaCarousel: MediaScrollView 141 @Mock lateinit var pageIndicator: PageIndicator 142 @Mock lateinit var mediaFlags: MediaFlags 143 @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor 144 @Mock lateinit var globalSettings: GlobalSettings 145 private val transitionRepository = kosmos.fakeKeyguardTransitionRepository 146 @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener> 147 @Captor 148 lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener> 149 @Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener> 150 @Captor lateinit var keyguardCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback> 151 @Captor lateinit var hostStateCallback: ArgumentCaptor<MediaHostStatesManager.Callback> 152 @Captor lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver> 153 154 private val clock = FakeSystemClock() 155 private lateinit var bgExecutor: FakeExecutor 156 private lateinit var uiExecutor: FakeExecutor 157 private lateinit var mediaCarouselController: MediaCarouselController 158 159 private var originalResumeSetting = 160 Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1) 161 162 companion object { 163 @JvmStatic 164 @Parameters(name = "{0}") getParamsnull165 fun getParams(): List<FlagsParameterization> { 166 return FlagsParameterization.progressionOf( 167 com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_UMO_INFLATION_IN_BACKGROUND 168 ) 169 } 170 } 171 172 init { 173 mSetFlagsRule.setFlagsParameterization(flags) 174 } 175 176 @Before setupnull177 fun setup() { 178 MockitoAnnotations.initMocks(this) 179 context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK)) 180 bgExecutor = FakeExecutor(clock) 181 uiExecutor = FakeExecutor(clock) 182 183 mediaCarouselController = 184 MediaCarouselController( 185 applicationScope = kosmos.applicationCoroutineScope, 186 context = context, 187 mediaControlPanelFactory = mediaControlPanelFactory, 188 visualStabilityProvider = visualStabilityProvider, 189 mediaHostStatesManager = mediaHostStatesManager, 190 activityStarter = activityStarter, 191 systemClock = clock, 192 mainDispatcher = kosmos.testDispatcher, 193 uiExecutor = uiExecutor, 194 bgExecutor = bgExecutor, 195 backgroundDispatcher = testDispatcher, 196 mediaManager = mediaDataManager, 197 configurationController = configurationController, 198 falsingManager = falsingManager, 199 dumpManager = dumpManager, 200 logger = logger, 201 debugLogger = debugLogger, 202 mediaFlags = mediaFlags, 203 keyguardUpdateMonitor = keyguardUpdateMonitor, 204 keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, 205 globalSettings = globalSettings, 206 secureSettings = secureSettings, 207 mediaCarouselViewModel = kosmos.mediaCarouselViewModel, 208 mediaViewControllerFactory = mediaViewControllerFactory, 209 deviceEntryInteractor = kosmos.deviceEntryInteractor, 210 ) 211 verify(configurationController).addCallback(capture(configListener)) 212 verify(visualStabilityProvider) 213 .addPersistentReorderingAllowedListener(capture(visualStabilityCallback)) 214 verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCallback)) 215 verify(mediaHostStatesManager).addCallback(capture(hostStateCallback)) 216 whenever(mediaControlPanelFactory.get()).thenReturn(panel) 217 whenever(panel.mediaViewController).thenReturn(mediaViewController) 218 whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false) 219 MediaPlayerData.clear() 220 FakeExecutor.exhaustExecutors(bgExecutor) 221 FakeExecutor.exhaustExecutors(uiExecutor) 222 verify(globalSettings) 223 .registerContentObserverSync( 224 eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)), 225 capture(settingsObserverCaptor), 226 ) 227 } 228 229 @After tearDownnull230 fun tearDown() { 231 Settings.Secure.putInt( 232 context.contentResolver, 233 Settings.Secure.MEDIA_CONTROLS_RESUME, 234 originalResumeSetting, 235 ) 236 } 237 238 @Test testPlayerOrderingnull239 fun testPlayerOrdering() { 240 // Test values: key, data, last active time 241 val playingLocal = 242 Triple( 243 PLAYING_LOCAL, 244 DATA.copy( 245 active = true, 246 isPlaying = true, 247 playbackLocation = MediaData.PLAYBACK_LOCAL, 248 resumption = false, 249 ), 250 4500L, 251 ) 252 253 val playingCast = 254 Triple( 255 "playing cast", 256 DATA.copy( 257 active = true, 258 isPlaying = true, 259 playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, 260 resumption = false, 261 ), 262 5000L, 263 ) 264 265 val pausedLocal = 266 Triple( 267 PAUSED_LOCAL, 268 DATA.copy( 269 active = true, 270 isPlaying = false, 271 playbackLocation = MediaData.PLAYBACK_LOCAL, 272 resumption = false, 273 ), 274 1000L, 275 ) 276 277 val pausedCast = 278 Triple( 279 "paused cast", 280 DATA.copy( 281 active = true, 282 isPlaying = false, 283 playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, 284 resumption = false, 285 ), 286 2000L, 287 ) 288 289 val playingRcn = 290 Triple( 291 "playing RCN", 292 DATA.copy( 293 active = true, 294 isPlaying = true, 295 playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, 296 resumption = false, 297 ), 298 5000L, 299 ) 300 301 val pausedRcn = 302 Triple( 303 "paused RCN", 304 DATA.copy( 305 active = true, 306 isPlaying = false, 307 playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, 308 resumption = false, 309 ), 310 5000L, 311 ) 312 313 val active = 314 Triple( 315 "active", 316 DATA.copy( 317 active = true, 318 isPlaying = false, 319 playbackLocation = MediaData.PLAYBACK_LOCAL, 320 resumption = true, 321 ), 322 250L, 323 ) 324 325 val resume1 = 326 Triple( 327 "resume 1", 328 DATA.copy( 329 active = false, 330 isPlaying = false, 331 playbackLocation = MediaData.PLAYBACK_LOCAL, 332 resumption = true, 333 ), 334 500L, 335 ) 336 337 val resume2 = 338 Triple( 339 "resume 2", 340 DATA.copy( 341 active = false, 342 isPlaying = false, 343 playbackLocation = MediaData.PLAYBACK_LOCAL, 344 resumption = true, 345 ), 346 1000L, 347 ) 348 349 val activeMoreRecent = 350 Triple( 351 "active more recent", 352 DATA.copy( 353 active = false, 354 isPlaying = false, 355 playbackLocation = MediaData.PLAYBACK_LOCAL, 356 resumption = true, 357 lastActive = 2L, 358 ), 359 1000L, 360 ) 361 362 val activeLessRecent = 363 Triple( 364 "active less recent", 365 DATA.copy( 366 active = false, 367 isPlaying = false, 368 playbackLocation = MediaData.PLAYBACK_LOCAL, 369 resumption = true, 370 lastActive = 1L, 371 ), 372 1000L, 373 ) 374 // Expected ordering for media players: 375 // Actively playing local sessions 376 // Actively playing cast sessions 377 // Paused local and cast sessions, by last active 378 // RCNs 379 // Resume controls, by last active 380 381 val expected = 382 listOf( 383 playingLocal, 384 playingCast, 385 pausedCast, 386 pausedLocal, 387 playingRcn, 388 pausedRcn, 389 active, 390 resume2, 391 resume1, 392 ) 393 394 expected.forEach { 395 clock.setCurrentTimeMillis(it.third) 396 MediaPlayerData.addMediaPlayer( 397 it.first, 398 it.second.copy(notificationKey = it.first), 399 panel, 400 clock, 401 isSsReactivated = false, 402 ) 403 } 404 405 for ((index, key) in MediaPlayerData.playerKeys().withIndex()) { 406 assertEquals(expected.get(index).first, key.data.notificationKey) 407 } 408 409 for ((index, key) in MediaPlayerData.visiblePlayerKeys().withIndex()) { 410 assertEquals(expected.get(index).first, key.data.notificationKey) 411 } 412 } 413 414 @Test testOrderWithSmartspace_prioritizednull415 fun testOrderWithSmartspace_prioritized() { 416 testPlayerOrdering() 417 418 // If smartspace is prioritized 419 MediaPlayerData.addMediaRecommendation( 420 SMARTSPACE_KEY, 421 EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true), 422 panel, 423 true, 424 clock, 425 ) 426 427 // Then it should be shown immediately after any actively playing controls 428 assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec) 429 } 430 431 @DisableSceneContainer 432 @Test testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayersnull433 fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() { 434 verify(mediaDataManager).addListener(capture(listener)) 435 436 testPlayerOrdering() 437 438 // If smartspace is prioritized 439 listener.value.onSmartspaceMediaDataLoaded( 440 SMARTSPACE_KEY, 441 EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true), 442 true, 443 ) 444 445 // Then it should be shown immediately after any actively playing controls 446 assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec) 447 assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec) 448 } 449 450 @Test testOrderWithSmartspace_notPrioritizednull451 fun testOrderWithSmartspace_notPrioritized() { 452 testPlayerOrdering() 453 454 // If smartspace is not prioritized 455 MediaPlayerData.addMediaRecommendation( 456 SMARTSPACE_KEY, 457 EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true), 458 panel, 459 false, 460 clock, 461 ) 462 463 // Then it should be shown at the end of the carousel's active entries 464 val idx = MediaPlayerData.playerKeys().count { it.data.active } - 1 465 assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec) 466 } 467 468 @DisableSceneContainer 469 @Test testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdatednull470 fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() { 471 verify(mediaDataManager).addListener(capture(listener)) 472 473 testPlayerOrdering() 474 // playing paused player 475 listener.value.onMediaDataLoaded( 476 PAUSED_LOCAL, 477 PAUSED_LOCAL, 478 DATA.copy( 479 active = true, 480 isPlaying = true, 481 playbackLocation = MediaData.PLAYBACK_LOCAL, 482 resumption = false, 483 ), 484 ) 485 listener.value.onMediaDataLoaded( 486 PLAYING_LOCAL, 487 PLAYING_LOCAL, 488 DATA.copy( 489 active = true, 490 isPlaying = false, 491 playbackLocation = MediaData.PLAYBACK_LOCAL, 492 resumption = true, 493 ), 494 ) 495 runAllReady() 496 497 assertEquals( 498 MediaPlayerData.getMediaPlayerIndex(PAUSED_LOCAL), 499 mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex, 500 ) 501 // paused player order should stays the same in visibleMediaPLayer map. 502 // paused player order should be first in mediaPlayer map. 503 assertEquals( 504 MediaPlayerData.visiblePlayerKeys().elementAt(3), 505 MediaPlayerData.playerKeys().elementAt(0), 506 ) 507 } 508 509 @Test testSwipeDismiss_loggednull510 fun testSwipeDismiss_logged() { 511 mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke() 512 513 verify(logger).logSwipeDismiss() 514 } 515 516 @Test testSettingsButton_loggednull517 fun testSettingsButton_logged() { 518 mediaCarouselController.settingsButton.callOnClick() 519 520 verify(logger).logCarouselSettings() 521 } 522 523 @Test testLocationChangeQs_loggednull524 fun testLocationChangeQs_logged() { 525 mediaCarouselController.onDesiredLocationChanged( 526 LOCATION_QS, 527 mediaHostState, 528 animate = false, 529 ) 530 bgExecutor.runAllReady() 531 verify(logger).logCarouselPosition(LOCATION_QS) 532 } 533 534 @Test testLocationChangeQqs_loggednull535 fun testLocationChangeQqs_logged() { 536 mediaCarouselController.onDesiredLocationChanged( 537 MediaHierarchyManager.LOCATION_QQS, 538 mediaHostState, 539 animate = false, 540 ) 541 bgExecutor.runAllReady() 542 verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS) 543 } 544 545 @Test testLocationChangeLockscreen_loggednull546 fun testLocationChangeLockscreen_logged() { 547 mediaCarouselController.onDesiredLocationChanged( 548 MediaHierarchyManager.LOCATION_LOCKSCREEN, 549 mediaHostState, 550 animate = false, 551 ) 552 bgExecutor.runAllReady() 553 verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN) 554 } 555 556 @Test testLocationChangeDream_loggednull557 fun testLocationChangeDream_logged() { 558 mediaCarouselController.onDesiredLocationChanged( 559 MediaHierarchyManager.LOCATION_DREAM_OVERLAY, 560 mediaHostState, 561 animate = false, 562 ) 563 bgExecutor.runAllReady() 564 verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY) 565 } 566 567 @Test testRecommendationRemoved_loggednull568 fun testRecommendationRemoved_logged() { 569 val packageName = "smartspace package" 570 val instanceId = InstanceId.fakeInstanceId(123) 571 572 val smartspaceData = 573 EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = packageName, instanceId = instanceId) 574 MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, smartspaceData, panel, true, clock) 575 mediaCarouselController.removePlayer(SMARTSPACE_KEY) 576 577 verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!)) 578 } 579 580 @DisableSceneContainer 581 @Test testMediaLoaded_ScrollToActivePlayernull582 fun testMediaLoaded_ScrollToActivePlayer() { 583 verify(mediaDataManager).addListener(capture(listener)) 584 585 listener.value.onMediaDataLoaded( 586 PLAYING_LOCAL, 587 null, 588 DATA.copy( 589 active = true, 590 isPlaying = true, 591 playbackLocation = MediaData.PLAYBACK_LOCAL, 592 resumption = false, 593 ), 594 ) 595 listener.value.onMediaDataLoaded( 596 PAUSED_LOCAL, 597 null, 598 DATA.copy( 599 active = true, 600 isPlaying = false, 601 playbackLocation = MediaData.PLAYBACK_LOCAL, 602 resumption = false, 603 ), 604 ) 605 runAllReady() 606 // adding a media recommendation card. 607 listener.value.onSmartspaceMediaDataLoaded( 608 SMARTSPACE_KEY, 609 EMPTY_SMARTSPACE_MEDIA_DATA, 610 false, 611 ) 612 mediaCarouselController.shouldScrollToKey = true 613 // switching between media players. 614 listener.value.onMediaDataLoaded( 615 PLAYING_LOCAL, 616 PLAYING_LOCAL, 617 DATA.copy( 618 active = true, 619 isPlaying = false, 620 playbackLocation = MediaData.PLAYBACK_LOCAL, 621 resumption = true, 622 ), 623 ) 624 listener.value.onMediaDataLoaded( 625 PAUSED_LOCAL, 626 PAUSED_LOCAL, 627 DATA.copy( 628 active = true, 629 isPlaying = true, 630 playbackLocation = MediaData.PLAYBACK_LOCAL, 631 resumption = false, 632 ), 633 ) 634 runAllReady() 635 636 assertEquals( 637 MediaPlayerData.getMediaPlayerIndex(PAUSED_LOCAL), 638 mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex, 639 ) 640 } 641 642 @DisableSceneContainer 643 @Test testMediaLoadedFromRecommendationCard_ScrollToActivePlayernull644 fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() { 645 verify(mediaDataManager).addListener(capture(listener)) 646 647 listener.value.onSmartspaceMediaDataLoaded( 648 SMARTSPACE_KEY, 649 EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true), 650 false, 651 ) 652 listener.value.onMediaDataLoaded( 653 PLAYING_LOCAL, 654 null, 655 DATA.copy( 656 active = true, 657 isPlaying = true, 658 playbackLocation = MediaData.PLAYBACK_LOCAL, 659 resumption = false, 660 ), 661 ) 662 runAllReady() 663 664 var playerIndex = MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL) 665 assertEquals( 666 playerIndex, 667 mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex, 668 ) 669 assertEquals(playerIndex, 0) 670 671 // Replaying the same media player one more time. 672 // And check that the card stays in its position. 673 mediaCarouselController.shouldScrollToKey = true 674 listener.value.onMediaDataLoaded( 675 PLAYING_LOCAL, 676 null, 677 DATA.copy( 678 active = true, 679 isPlaying = true, 680 playbackLocation = MediaData.PLAYBACK_LOCAL, 681 resumption = false, 682 packageName = "PACKAGE_NAME", 683 ), 684 ) 685 runAllReady() 686 playerIndex = MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL) 687 assertEquals(playerIndex, 0) 688 } 689 690 @DisableSceneContainer 691 @Test testRecommendationRemovedWhileNotVisible_updateHostVisibilitynull692 fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() { 693 verify(mediaDataManager).addListener(capture(listener)) 694 695 var result = false 696 mediaCarouselController.updateHostVisibility = { result = true } 697 698 whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true) 699 listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false) 700 701 assertEquals(true, result) 702 } 703 704 @DisableSceneContainer 705 @Test testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibilitynull706 fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() { 707 verify(mediaDataManager).addListener(capture(listener)) 708 709 var result = false 710 mediaCarouselController.updateHostVisibility = { result = true } 711 712 whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false) 713 listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false) 714 assertEquals(false, result) 715 716 visualStabilityCallback.value.onReorderingAllowed() 717 assertEquals(true, result) 718 } 719 720 @Test testGetCurrentVisibleMediaContentIntentnull721 fun testGetCurrentVisibleMediaContentIntent() { 722 val clickIntent1 = mock(PendingIntent::class.java) 723 val player1 = Triple("player1", DATA.copy(clickIntent = clickIntent1), 1000L) 724 clock.setCurrentTimeMillis(player1.third) 725 MediaPlayerData.addMediaPlayer( 726 player1.first, 727 player1.second.copy(notificationKey = player1.first), 728 panel, 729 clock, 730 isSsReactivated = false, 731 ) 732 733 assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1) 734 735 val clickIntent2 = mock(PendingIntent::class.java) 736 val player2 = Triple("player2", DATA.copy(clickIntent = clickIntent2), 2000L) 737 clock.setCurrentTimeMillis(player2.third) 738 MediaPlayerData.addMediaPlayer( 739 player2.first, 740 player2.second.copy(notificationKey = player2.first), 741 panel, 742 clock, 743 isSsReactivated = false, 744 ) 745 746 // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is 747 // added to the front because it was active more recently. 748 assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2) 749 750 val clickIntent3 = mock(PendingIntent::class.java) 751 val player3 = Triple("player3", DATA.copy(clickIntent = clickIntent3), 500L) 752 clock.setCurrentTimeMillis(player3.third) 753 MediaPlayerData.addMediaPlayer( 754 player3.first, 755 player3.second.copy(notificationKey = player3.first), 756 panel, 757 clock, 758 isSsReactivated = false, 759 ) 760 761 // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is 762 // added to the end because it was active less recently. 763 assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2) 764 } 765 766 @Test testSetCurrentState_UpdatePageIndicatorAlphaWhenSquishnull767 fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() { 768 val delta = 0.0001F 769 mediaCarouselController.mediaCarousel = mediaCarousel 770 mediaCarouselController.pageIndicator = pageIndicator 771 whenever(mediaCarousel.measuredHeight).thenReturn(100) 772 whenever(pageIndicator.translationY).thenReturn(80F) 773 whenever(pageIndicator.height).thenReturn(10) 774 whenever(mediaHostStatesManager.mediaHostStates) 775 .thenReturn(mutableMapOf(LOCATION_QS to mediaHostState)) 776 whenever(mediaHostState.visible).thenReturn(true) 777 mediaCarouselController.currentEndLocation = LOCATION_QS 778 whenever(mediaHostState.squishFraction).thenReturn(0.938F) 779 mediaCarouselController.updatePageIndicatorAlpha() 780 verify(pageIndicator).alpha = floatThat { abs(it - 0.5F) < delta } 781 782 whenever(mediaHostState.squishFraction).thenReturn(1.0F) 783 mediaCarouselController.updatePageIndicatorAlpha() 784 verify(pageIndicator).alpha = floatThat { abs(it - 1.0F) < delta } 785 } 786 787 @Test testOnConfigChanged_playersAreAddedBacknull788 fun testOnConfigChanged_playersAreAddedBack() { 789 testConfigurationChange { configListener.value.onConfigChanged(Configuration()) } 790 } 791 792 @Test testOnUiModeChanged_playersAreAddedBacknull793 fun testOnUiModeChanged_playersAreAddedBack() { 794 testConfigurationChange(configListener.value::onUiModeChanged) 795 796 verify(pageIndicator).tintList = 797 ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator)) 798 verify(pageIndicator, times(2)).setNumPages(any()) 799 } 800 801 @Test testOnDensityOrFontScaleChanged_playersAreAddedBacknull802 fun testOnDensityOrFontScaleChanged_playersAreAddedBack() { 803 testConfigurationChange(configListener.value::onDensityOrFontScaleChanged) 804 805 verify(pageIndicator).tintList = 806 ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator)) 807 // when recreateMedia is set to true, page indicator is updated on removal and addition. 808 verify(pageIndicator, times(4)).setNumPages(any()) 809 } 810 811 @Test testOnThemeChanged_playersAreAddedBacknull812 fun testOnThemeChanged_playersAreAddedBack() { 813 testConfigurationChange(configListener.value::onThemeChanged) 814 815 verify(pageIndicator).tintList = 816 ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator)) 817 verify(pageIndicator, times(2)).setNumPages(any()) 818 } 819 820 @Test testOnLocaleListChanged_playersAreAddedBacknull821 fun testOnLocaleListChanged_playersAreAddedBack() { 822 context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK, Locale.CANADA)) 823 testConfigurationChange(configListener.value::onLocaleListChanged) 824 825 verify(pageIndicator, never()).tintList = 826 ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator)) 827 828 context.resources.configuration.setLocales(LocaleList(Locale.UK, Locale.US, Locale.CANADA)) 829 testConfigurationChange(configListener.value::onLocaleListChanged) 830 831 verify(pageIndicator).tintList = 832 ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator)) 833 // When recreateMedia is set to true, page indicator is updated on removal and addition. 834 verify(pageIndicator, times(4)).setNumPages(any()) 835 } 836 837 @DisableSceneContainer 838 @Test testRecommendation_persistentEnabled_newSmartspaceLoaded_updatesSortnull839 fun testRecommendation_persistentEnabled_newSmartspaceLoaded_updatesSort() { 840 verify(mediaDataManager).addListener(capture(listener)) 841 842 testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded() 843 844 // When an update to existing smartspace data is loaded 845 listener.value.onSmartspaceMediaDataLoaded( 846 SMARTSPACE_KEY, 847 EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true), 848 true, 849 ) 850 851 // Then the carousel is updated 852 assertTrue(MediaPlayerData.playerKeys().elementAt(0).data.active) 853 assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active) 854 } 855 856 @DisableSceneContainer 857 @Test testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAddednull858 fun testRecommendation_persistentEnabled_inactiveSmartspaceDataLoaded_isAdded() { 859 verify(mediaDataManager).addListener(capture(listener)) 860 861 whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true) 862 863 // When inactive smartspace data is loaded 864 listener.value.onSmartspaceMediaDataLoaded( 865 SMARTSPACE_KEY, 866 EMPTY_SMARTSPACE_MEDIA_DATA, 867 false, 868 ) 869 870 // Then it is added to the carousel with correct state 871 assertTrue(MediaPlayerData.playerKeys().elementAt(0).isSsMediaRec) 872 assertFalse(MediaPlayerData.playerKeys().elementAt(0).data.active) 873 874 assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).isSsMediaRec) 875 assertFalse(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active) 876 } 877 878 @Test testOnLockDownMode_hideMediaCarouselnull879 fun testOnLockDownMode_hideMediaCarousel() { 880 whenever(keyguardUpdateMonitor.isUserInLockdown(context.userId)).thenReturn(true) 881 mediaCarouselController.mediaCarousel = mediaCarousel 882 883 keyguardCallback.value.onStrongAuthStateChanged(context.userId) 884 885 verify(mediaCarousel).visibility = View.GONE 886 } 887 888 @Test testLockDownModeOff_showMediaCarouselnull889 fun testLockDownModeOff_showMediaCarousel() { 890 whenever(keyguardUpdateMonitor.isUserInLockdown(context.userId)).thenReturn(false) 891 whenever(keyguardUpdateMonitor.isUserUnlocked(context.userId)).thenReturn(true) 892 mediaCarouselController.mediaCarousel = mediaCarousel 893 894 keyguardCallback.value.onStrongAuthStateChanged(context.userId) 895 896 verify(mediaCarousel).visibility = View.VISIBLE 897 } 898 899 @DisableSceneContainer 900 @Test testKeyguardGone_showMediaCarouselnull901 fun testKeyguardGone_showMediaCarousel() = 902 kosmos.testScope.runTest { 903 kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) 904 var updatedVisibility = false 905 mediaCarouselController.updateHostVisibility = { updatedVisibility = true } 906 mediaCarouselController.mediaCarousel = mediaCarousel 907 908 val job = mediaCarouselController.listenForAnyStateToGoneKeyguardTransition(this) 909 transitionRepository.sendTransitionSteps( 910 from = KeyguardState.LOCKSCREEN, 911 to = KeyguardState.GONE, 912 this, 913 ) 914 915 verify(mediaCarousel).visibility = View.VISIBLE 916 assertEquals(true, updatedVisibility) 917 assertEquals(false, mediaCarouselController.isLockedAndHidden()) 918 919 job.cancel() 920 } 921 922 @EnableSceneContainer 923 @Test testKeyguardGone_showMediaCarousel_scene_containernull924 fun testKeyguardGone_showMediaCarousel_scene_container() = 925 kosmos.testScope.runTest { 926 kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) 927 var updatedVisibility = false 928 mediaCarouselController.updateHostVisibility = { updatedVisibility = true } 929 mediaCarouselController.mediaCarousel = mediaCarousel 930 931 val job = mediaCarouselController.listenForAnyStateToGoneKeyguardTransition(this) 932 kosmos.setSceneTransition(Idle(Scenes.Gone)) 933 934 verify(mediaCarousel).visibility = View.VISIBLE 935 assertEquals(true, updatedVisibility) 936 937 job.cancel() 938 } 939 940 @Test keyguardShowing_notAllowedOnLockscreen_updateVisibilitynull941 fun keyguardShowing_notAllowedOnLockscreen_updateVisibility() { 942 kosmos.testScope.runTest { 943 kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) 944 var updatedVisibility = false 945 mediaCarouselController.updateHostVisibility = { updatedVisibility = true } 946 mediaCarouselController.mediaCarousel = mediaCarousel 947 948 val settingsJob = 949 mediaCarouselController.listenForLockscreenSettingChanges( 950 kosmos.applicationCoroutineScope 951 ) 952 secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, false) 953 954 val keyguardJob = mediaCarouselController.listenForAnyStateToLockscreenTransition(this) 955 transitionRepository.sendTransitionSteps( 956 from = KeyguardState.GONE, 957 to = KeyguardState.LOCKSCREEN, 958 this, 959 ) 960 961 assertEquals(true, updatedVisibility) 962 assertEquals(true, mediaCarouselController.isLockedAndHidden()) 963 964 settingsJob.cancel() 965 keyguardJob.cancel() 966 } 967 } 968 969 @Test keyguardShowing_allowedOnLockscreen_updateVisibilitynull970 fun keyguardShowing_allowedOnLockscreen_updateVisibility() { 971 kosmos.testScope.runTest { 972 kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) 973 var updatedVisibility = false 974 mediaCarouselController.updateHostVisibility = { updatedVisibility = true } 975 mediaCarouselController.mediaCarousel = mediaCarousel 976 977 val settingsJob = 978 mediaCarouselController.listenForLockscreenSettingChanges( 979 kosmos.applicationCoroutineScope 980 ) 981 secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, true) 982 983 val keyguardJob = mediaCarouselController.listenForAnyStateToLockscreenTransition(this) 984 transitionRepository.sendTransitionSteps( 985 from = KeyguardState.GONE, 986 to = KeyguardState.LOCKSCREEN, 987 this, 988 ) 989 990 assertEquals(true, updatedVisibility) 991 assertEquals(false, mediaCarouselController.isLockedAndHidden()) 992 993 settingsJob.cancel() 994 keyguardJob.cancel() 995 } 996 } 997 998 @EnableSceneContainer 999 @Test deviceEntered_mediaAllowed_notLockedAndHiddennull1000 fun deviceEntered_mediaAllowed_notLockedAndHidden() { 1001 kosmos.testScope.runTest { 1002 val settingsJob = 1003 mediaCarouselController.listenForLockscreenSettingChanges( 1004 kosmos.applicationCoroutineScope 1005 ) 1006 secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, true) 1007 setDeviceEntered(true) 1008 1009 assertEquals(false, mediaCarouselController.isLockedAndHidden()) 1010 1011 settingsJob.cancel() 1012 } 1013 } 1014 1015 @EnableSceneContainer 1016 @Test deviceEntered_mediaNotAllowed_notLockedAndHiddennull1017 fun deviceEntered_mediaNotAllowed_notLockedAndHidden() { 1018 kosmos.testScope.runTest { 1019 val settingsJob = 1020 mediaCarouselController.listenForLockscreenSettingChanges( 1021 kosmos.applicationCoroutineScope 1022 ) 1023 secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, false) 1024 setDeviceEntered(true) 1025 1026 assertEquals(false, mediaCarouselController.isLockedAndHidden()) 1027 1028 settingsJob.cancel() 1029 } 1030 } 1031 1032 @EnableSceneContainer 1033 @Test deviceNotEntered_mediaAllowed_notLockedAndHiddennull1034 fun deviceNotEntered_mediaAllowed_notLockedAndHidden() { 1035 kosmos.testScope.runTest { 1036 val settingsJob = 1037 mediaCarouselController.listenForLockscreenSettingChanges( 1038 kosmos.applicationCoroutineScope 1039 ) 1040 secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, true) 1041 setDeviceEntered(false) 1042 1043 assertEquals(false, mediaCarouselController.isLockedAndHidden()) 1044 1045 settingsJob.cancel() 1046 } 1047 } 1048 1049 @EnableSceneContainer 1050 @Test deviceNotEntered_mediaNotAllowed_lockedAndHiddennull1051 fun deviceNotEntered_mediaNotAllowed_lockedAndHidden() { 1052 kosmos.testScope.runTest { 1053 val settingsJob = 1054 mediaCarouselController.listenForLockscreenSettingChanges( 1055 kosmos.applicationCoroutineScope 1056 ) 1057 secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, false) 1058 setDeviceEntered(false) 1059 1060 assertEquals(true, mediaCarouselController.isLockedAndHidden()) 1061 1062 settingsJob.cancel() 1063 } 1064 } 1065 1066 @Test testInvisibleToUserAndExpanded_playersNotListeningnull1067 fun testInvisibleToUserAndExpanded_playersNotListening() { 1068 // Add players to carousel. 1069 testPlayerOrdering() 1070 1071 // Make the carousel visible to user in expanded layout. 1072 mediaCarouselController.currentlyExpanded = true 1073 mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = true 1074 1075 // panel is the player for each MediaPlayerData. 1076 // Verify that seekbar listening attribute in media control panel is set to true. 1077 verify(panel, times(MediaPlayerData.players().size)).listening = true 1078 1079 // Make the carousel invisible to user. 1080 mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = false 1081 1082 // panel is the player for each MediaPlayerData. 1083 // Verify that seekbar listening attribute in media control panel is set to false. 1084 verify(panel, times(MediaPlayerData.players().size)).listening = false 1085 } 1086 1087 @Test testVisibleToUserAndExpanded_playersListeningnull1088 fun testVisibleToUserAndExpanded_playersListening() { 1089 // Add players to carousel. 1090 testPlayerOrdering() 1091 1092 // Make the carousel visible to user in expanded layout. 1093 mediaCarouselController.currentlyExpanded = true 1094 mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = true 1095 1096 // panel is the player for each MediaPlayerData. 1097 // Verify that seekbar listening attribute in media control panel is set to true. 1098 verify(panel, times(MediaPlayerData.players().size)).listening = true 1099 } 1100 1101 @Test testUMOCollapsed_playersNotListeningnull1102 fun testUMOCollapsed_playersNotListening() { 1103 // Add players to carousel. 1104 testPlayerOrdering() 1105 1106 // Make the carousel in collapsed layout. 1107 mediaCarouselController.currentlyExpanded = false 1108 1109 // panel is the player for each MediaPlayerData. 1110 // Verify that seekbar listening attribute in media control panel is set to false. 1111 verify(panel, times(MediaPlayerData.players().size)).listening = false 1112 1113 // Make the carousel visible to user. 1114 reset(panel) 1115 mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = true 1116 1117 // Verify that seekbar listening attribute in media control panel is set to false. 1118 verify(panel, times(MediaPlayerData.players().size)).listening = false 1119 } 1120 1121 @Test testOnHostStateChanged_updateVisibilitynull1122 fun testOnHostStateChanged_updateVisibility() { 1123 var stateUpdated = false 1124 mediaCarouselController.updateUserVisibility = { stateUpdated = true } 1125 1126 // When the host state updates 1127 hostStateCallback.value!!.onHostStateChanged(LOCATION_QS, mediaHostState) 1128 1129 // Then the carousel visibility is updated 1130 assertTrue(stateUpdated) 1131 } 1132 1133 @Test testAnimationScaleChanged_mediaControlPanelsNotifiednull1134 fun testAnimationScaleChanged_mediaControlPanelsNotified() { 1135 MediaPlayerData.addMediaPlayer("key", DATA, panel, clock, isSsReactivated = false) 1136 1137 globalSettings.putFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 0f) 1138 settingsObserverCaptor.value!!.onChange(false) 1139 verify(panel).updateAnimatorDurationScale() 1140 } 1141 1142 @DisableSceneContainer 1143 @Test swipeToDismiss_pausedAndResumeOff_userInitiatednull1144 fun swipeToDismiss_pausedAndResumeOff_userInitiated() { 1145 verify(mediaDataManager).addListener(capture(listener)) 1146 1147 // When resumption is disabled, paused media should be dismissed after being swiped away 1148 Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0) 1149 val pausedMedia = DATA.copy(isPlaying = false) 1150 listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia) 1151 runAllReady() 1152 mediaCarouselController.onSwipeToDismiss() 1153 1154 // When it can be removed immediately on update 1155 whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true) 1156 val inactiveMedia = pausedMedia.copy(active = false) 1157 listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia) 1158 runAllReady() 1159 1160 // This is processed as a user-initiated dismissal 1161 verify(debugLogger).logMediaRemoved(eq(PAUSED_LOCAL), eq(true)) 1162 verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true)) 1163 } 1164 1165 @DisableSceneContainer 1166 @Test swipeToDismiss_pausedAndResumeOff_delayed_userInitiatednull1167 fun swipeToDismiss_pausedAndResumeOff_delayed_userInitiated() { 1168 verify(mediaDataManager).addListener(capture(listener)) 1169 1170 // When resumption is disabled, paused media should be dismissed after being swiped away 1171 Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0) 1172 mediaCarouselController.updateHostVisibility = {} 1173 1174 val pausedMedia = DATA.copy(isPlaying = false) 1175 listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia) 1176 runAllReady() 1177 mediaCarouselController.onSwipeToDismiss() 1178 1179 // When it can't be removed immediately on update 1180 whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false) 1181 val inactiveMedia = pausedMedia.copy(active = false) 1182 listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia) 1183 runAllReady() 1184 visualStabilityCallback.value.onReorderingAllowed() 1185 1186 // This is processed as a user-initiated dismissal 1187 verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true)) 1188 } 1189 1190 /** 1191 * Helper method when a configuration change occurs. 1192 * 1193 * @param function called when a certain configuration change occurs. 1194 */ testConfigurationChangenull1195 private fun testConfigurationChange(function: () -> Unit) { 1196 verify(mediaDataManager).addListener(capture(listener)) 1197 mediaCarouselController.pageIndicator = pageIndicator 1198 listener.value.onMediaDataLoaded( 1199 PLAYING_LOCAL, 1200 null, 1201 DATA.copy( 1202 active = true, 1203 isPlaying = true, 1204 playbackLocation = MediaData.PLAYBACK_LOCAL, 1205 resumption = false, 1206 ), 1207 ) 1208 listener.value.onMediaDataLoaded( 1209 PAUSED_LOCAL, 1210 null, 1211 DATA.copy( 1212 active = true, 1213 isPlaying = false, 1214 playbackLocation = MediaData.PLAYBACK_LOCAL, 1215 resumption = false, 1216 ), 1217 ) 1218 runAllReady() 1219 1220 val playersSize = MediaPlayerData.players().size 1221 reset(pageIndicator) 1222 function() 1223 runAllReady() 1224 1225 assertEquals(playersSize, MediaPlayerData.players().size) 1226 assertEquals( 1227 MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL), 1228 mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex, 1229 ) 1230 } 1231 TestScopenull1232 private fun TestScope.setDeviceEntered(isEntered: Boolean) { 1233 if (isEntered) { 1234 // Unlock the device, marking the device as entered 1235 kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( 1236 SuccessFingerprintAuthenticationStatus(0, true) 1237 ) 1238 runCurrent() 1239 } 1240 setScene( 1241 if (isEntered) { 1242 Scenes.Gone 1243 } else { 1244 Scenes.Lockscreen 1245 } 1246 ) 1247 assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered) 1248 } 1249 setScenenull1250 private fun TestScope.setScene(key: SceneKey) { 1251 kosmos.sceneInteractor.changeScene(key, "test") 1252 kosmos.sceneInteractor.setTransitionState( 1253 MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) 1254 ) 1255 runCurrent() 1256 } 1257 runAllReadynull1258 private fun runAllReady() { 1259 if (mediaControlsUmoInflationInBackground()) { 1260 bgExecutor.runAllReady() 1261 uiExecutor.runAllReady() 1262 } 1263 } 1264 } 1265