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.ui.controller 18 19 import android.graphics.Rect 20 import android.provider.Settings 21 import android.testing.TestableLooper 22 import android.view.ViewGroup 23 import android.widget.FrameLayout 24 import androidx.test.ext.junit.runners.AndroidJUnit4 25 import androidx.test.filters.SmallTest 26 import com.android.keyguard.KeyguardViewController 27 import com.android.systemui.SysuiTestCase 28 import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository 29 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository 30 import com.android.systemui.communal.shared.model.CommunalScenes 31 import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel 32 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq 33 import com.android.systemui.dreams.DreamOverlayStateController 34 import com.android.systemui.flags.DisableSceneContainer 35 import com.android.systemui.keyguard.WakefulnessLifecycle 36 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository 37 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository 38 import com.android.systemui.keyguard.data.repository.keyguardRepository 39 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor 40 import com.android.systemui.keyguard.shared.model.KeyguardState 41 import com.android.systemui.kosmos.testScope 42 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager 43 import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler 44 import com.android.systemui.media.controls.ui.view.MediaHost 45 import com.android.systemui.media.controls.ui.view.MediaHostState 46 import com.android.systemui.media.dream.MediaDreamComplication 47 import com.android.systemui.plugins.statusbar.StatusBarStateController 48 import com.android.systemui.res.R 49 import com.android.systemui.shade.domain.interactor.ShadeInteractor 50 import com.android.systemui.statusbar.StatusBarState 51 import com.android.systemui.statusbar.SysuiStatusBarStateController 52 import com.android.systemui.statusbar.phone.KeyguardBypassController 53 import com.android.systemui.statusbar.policy.FakeConfigurationController 54 import com.android.systemui.statusbar.policy.KeyguardStateController 55 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController 56 import com.android.systemui.testKosmos 57 import com.android.systemui.util.animation.UniqueObjectHostView 58 import com.android.systemui.util.mockito.mock 59 import com.android.systemui.util.mockito.nullable 60 import com.android.systemui.util.settings.FakeSettings 61 import com.android.systemui.utils.os.FakeHandler 62 import com.google.common.truth.Truth.assertThat 63 import kotlinx.coroutines.ExperimentalCoroutinesApi 64 import kotlinx.coroutines.flow.MutableStateFlow 65 import kotlinx.coroutines.test.runCurrent 66 import kotlinx.coroutines.test.runTest 67 import org.junit.Assert.assertNotNull 68 import org.junit.Before 69 import org.junit.Rule 70 import org.junit.Test 71 import org.junit.runner.RunWith 72 import org.mockito.ArgumentCaptor 73 import org.mockito.ArgumentMatchers 74 import org.mockito.ArgumentMatchers.anyBoolean 75 import org.mockito.ArgumentMatchers.anyLong 76 import org.mockito.Captor 77 import org.mockito.Mock 78 import org.mockito.Mockito.clearInvocations 79 import org.mockito.Mockito.times 80 import org.mockito.Mockito.verify 81 import org.mockito.Mockito.`when` as whenever 82 import org.mockito.junit.MockitoJUnit 83 import org.mockito.kotlin.any 84 import org.mockito.kotlin.anyOrNull 85 import org.mockito.kotlin.atLeastOnce 86 import org.mockito.kotlin.lastValue 87 88 @OptIn(ExperimentalCoroutinesApi::class) 89 @SmallTest 90 @RunWith(AndroidJUnit4::class) 91 @TestableLooper.RunWithLooper(setAsMainLooper = true) 92 @DisableSceneContainer 93 class MediaHierarchyManagerTest : SysuiTestCase() { 94 95 private val kosmos = testKosmos() 96 97 @Mock private lateinit var lockHost: MediaHost 98 @Mock private lateinit var qsHost: MediaHost 99 @Mock private lateinit var qqsHost: MediaHost 100 @Mock private lateinit var hubModeHost: MediaHost 101 @Mock private lateinit var bypassController: KeyguardBypassController 102 @Mock private lateinit var keyguardStateController: KeyguardStateController 103 @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController 104 @Mock private lateinit var mediaCarouselController: MediaCarouselController 105 @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler 106 @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle 107 @Mock private lateinit var keyguardViewController: KeyguardViewController 108 @Mock private lateinit var mediaDataManager: MediaDataManager 109 @Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView 110 @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController 111 @Mock private lateinit var shadeInteractor: ShadeInteractor 112 @Mock lateinit var logger: MediaViewLogger 113 @Captor 114 private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)> 115 @Captor 116 private lateinit var statusBarCallback: ArgumentCaptor<(StatusBarStateController.StateListener)> 117 @Captor 118 private lateinit var dreamOverlayCallback: 119 ArgumentCaptor<(DreamOverlayStateController.Callback)> 120 @JvmField @Rule val mockito = MockitoJUnit.rule() 121 private val testScope = kosmos.testScope 122 private lateinit var mediaHierarchyManager: MediaHierarchyManager 123 private lateinit var isQsBypassingShade: MutableStateFlow<Boolean> 124 private lateinit var shadeExpansion: MutableStateFlow<Float> 125 private lateinit var anyShadeExpanded: MutableStateFlow<Boolean> 126 private lateinit var mediaFrame: ViewGroup 127 private val configurationController = FakeConfigurationController() 128 private val settings = FakeSettings() 129 private lateinit var testableLooper: TestableLooper 130 private lateinit var fakeHandler: FakeHandler 131 private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository 132 private val keyguardRepository = kosmos.fakeKeyguardRepository 133 134 @Before setupnull135 fun setup() { 136 context 137 .getOrCreateTestableResources() 138 .addOverride(R.bool.config_use_split_notification_shade, false) 139 mediaFrame = FrameLayout(context) 140 testableLooper = TestableLooper.get(this) 141 fakeHandler = FakeHandler(testableLooper.looper) 142 whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame) 143 isQsBypassingShade = MutableStateFlow(false) 144 shadeExpansion = MutableStateFlow(0f) 145 anyShadeExpanded = MutableStateFlow(false) 146 whenever(shadeInteractor.isQsBypassingShade).thenReturn(isQsBypassingShade) 147 whenever(shadeInteractor.shadeExpansion).thenReturn(shadeExpansion) 148 whenever(shadeInteractor.isAnyFullyExpanded).thenReturn(anyShadeExpanded) 149 mediaHierarchyManager = 150 MediaHierarchyManager( 151 context, 152 statusBarStateController, 153 keyguardStateController, 154 bypassController, 155 mediaCarouselController, 156 mediaDataManager, 157 keyguardViewController, 158 dreamOverlayStateController, 159 kosmos.keyguardInteractor, 160 kosmos.communalTransitionViewModel, 161 configurationController, 162 wakefulnessLifecycle, 163 shadeInteractor, 164 settings, 165 fakeHandler, 166 testScope.backgroundScope, 167 ResourcesSplitShadeStateController(), 168 logger, 169 ) 170 verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture()) 171 verify(statusBarStateController).addCallback(statusBarCallback.capture()) 172 verify(dreamOverlayStateController).addCallback(dreamOverlayCallback.capture()) 173 setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP) 174 setupHost(qsHost, MediaHierarchyManager.LOCATION_QS, QS_TOP) 175 setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP) 176 setupHost(hubModeHost, MediaHierarchyManager.LOCATION_COMMUNAL_HUB, COMMUNAL_TOP) 177 whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) 178 whenever(mediaDataManager.hasActiveMedia()).thenReturn(true) 179 whenever(mediaCarouselController.mediaCarouselScrollHandler) 180 .thenReturn(mediaCarouselScrollHandler) 181 val observer = wakefullnessObserver.value 182 assertNotNull("lifecycle observer wasn't registered", observer) 183 observer.onFinishedWakingUp() 184 // We'll use the viewmanager to verify a few calls below, let's reset this. 185 clearInvocations(mediaCarouselController) 186 } 187 setupHostnull188 private fun setupHost(host: MediaHost, location: Int, top: Int) { 189 whenever(host.location).thenReturn(location) 190 whenever(host.currentBounds).thenReturn(Rect(0, top, 0, top)) 191 whenever(host.hostView).thenReturn(uniqueObjectHostView) 192 whenever(host.visible).thenReturn(true) 193 mediaHierarchyManager.register(host) 194 } 195 196 @Test testHostViewSetOnRegisternull197 fun testHostViewSetOnRegister() { 198 val host = mediaHierarchyManager.register(lockHost) 199 verify(lockHost).hostView = eq(host) 200 } 201 202 @Test testBlockedWhenScreenTurningOffnull203 fun testBlockedWhenScreenTurningOff() { 204 // Let's set it onto QS: 205 mediaHierarchyManager.qsExpansion = 1.0f 206 verify(mediaCarouselController) 207 .onDesiredLocationChanged( 208 ArgumentMatchers.anyInt(), 209 any<MediaHostState>(), 210 anyBoolean(), 211 anyLong(), 212 anyLong() 213 ) 214 val observer = wakefullnessObserver.value 215 assertNotNull("lifecycle observer wasn't registered", observer) 216 observer.onStartedGoingToSleep() 217 clearInvocations(mediaCarouselController) 218 mediaHierarchyManager.qsExpansion = 0.0f 219 verify(mediaCarouselController, times(0)) 220 .onDesiredLocationChanged( 221 ArgumentMatchers.anyInt(), 222 any<MediaHostState>(), 223 anyBoolean(), 224 anyLong(), 225 anyLong() 226 ) 227 } 228 229 @Test testBlockedWhenConfigurationChangesAndScreenOffnull230 fun testBlockedWhenConfigurationChangesAndScreenOff() { 231 // Let's set it onto QS: 232 mediaHierarchyManager.qsExpansion = 1.0f 233 verify(mediaCarouselController) 234 .onDesiredLocationChanged( 235 ArgumentMatchers.anyInt(), 236 any<MediaHostState>(), 237 anyBoolean(), 238 anyLong(), 239 anyLong() 240 ) 241 val observer = wakefullnessObserver.value 242 assertNotNull("lifecycle observer wasn't registered", observer) 243 observer.onStartedGoingToSleep() 244 clearInvocations(mediaCarouselController) 245 configurationController.notifyConfigurationChanged() 246 verify(mediaCarouselController, times(0)) 247 .onDesiredLocationChanged( 248 ArgumentMatchers.anyInt(), 249 any<MediaHostState>(), 250 anyBoolean(), 251 anyLong(), 252 anyLong() 253 ) 254 } 255 256 @Test testAllowedWhenConfigurationChangesnull257 fun testAllowedWhenConfigurationChanges() { 258 // Let's set it onto QS: 259 mediaHierarchyManager.qsExpansion = 1.0f 260 verify(mediaCarouselController) 261 .onDesiredLocationChanged( 262 ArgumentMatchers.anyInt(), 263 any<MediaHostState>(), 264 anyBoolean(), 265 anyLong(), 266 anyLong() 267 ) 268 clearInvocations(mediaCarouselController) 269 configurationController.notifyConfigurationChanged() 270 verify(mediaCarouselController) 271 .onDesiredLocationChanged( 272 ArgumentMatchers.anyInt(), 273 any<MediaHostState>(), 274 anyBoolean(), 275 anyLong(), 276 anyLong() 277 ) 278 } 279 280 @Test testAllowedWhenNotTurningOffnull281 fun testAllowedWhenNotTurningOff() { 282 // Let's set it onto QS: 283 mediaHierarchyManager.qsExpansion = 1.0f 284 verify(mediaCarouselController) 285 .onDesiredLocationChanged( 286 ArgumentMatchers.anyInt(), 287 any<MediaHostState>(), 288 anyBoolean(), 289 anyLong(), 290 anyLong() 291 ) 292 val observer = wakefullnessObserver.value 293 assertNotNull("lifecycle observer wasn't registered", observer) 294 clearInvocations(mediaCarouselController) 295 mediaHierarchyManager.qsExpansion = 0.0f 296 verify(mediaCarouselController) 297 .onDesiredLocationChanged( 298 ArgumentMatchers.anyInt(), 299 any<MediaHostState>(), 300 anyBoolean(), 301 anyLong(), 302 anyLong() 303 ) 304 } 305 306 @Test testGoingToFullShadenull307 fun testGoingToFullShade() { 308 goToLockscreen() 309 310 // Let's transition all the way to full shade 311 mediaHierarchyManager.setTransitionToFullShadeAmount(100000f) 312 verify(mediaCarouselController) 313 .onDesiredLocationChanged( 314 eq(MediaHierarchyManager.LOCATION_QQS), 315 any<MediaHostState>(), 316 eq(false), 317 anyLong(), 318 anyLong() 319 ) 320 clearInvocations(mediaCarouselController) 321 322 // Let's go back to the lock screen 323 mediaHierarchyManager.setTransitionToFullShadeAmount(0.0f) 324 verify(mediaCarouselController) 325 .onDesiredLocationChanged( 326 eq(MediaHierarchyManager.LOCATION_LOCKSCREEN), 327 any<MediaHostState>(), 328 eq(false), 329 anyLong(), 330 anyLong() 331 ) 332 333 // Let's make sure alpha is set 334 mediaHierarchyManager.setTransitionToFullShadeAmount(2.0f) 335 assertThat(mediaFrame.alpha).isNotEqualTo(1.0f) 336 } 337 338 @Test testTransformationOnLockScreenIsFadingnull339 fun testTransformationOnLockScreenIsFading() { 340 goToLockscreen() 341 expandQS() 342 343 val transformType = mediaHierarchyManager.calculateTransformationType() 344 assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE) 345 } 346 347 @Test calculateTransformationType_notOnLockscreen_returnsTransitionnull348 fun calculateTransformationType_notOnLockscreen_returnsTransition() { 349 expandQS() 350 351 val transformType = mediaHierarchyManager.calculateTransformationType() 352 353 assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION) 354 } 355 356 @Test calculateTransformationType_onLockscreen_returnsTransitionnull357 fun calculateTransformationType_onLockscreen_returnsTransition() { 358 goToLockscreen() 359 expandQS() 360 361 val transformType = mediaHierarchyManager.calculateTransformationType() 362 363 assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE) 364 } 365 366 @Test calculateTransformationType_onLockShade_inSplitShade_goingToFullShade_returnsTransitionnull367 fun calculateTransformationType_onLockShade_inSplitShade_goingToFullShade_returnsTransition() { 368 enableSplitShade() 369 goToLockscreen() 370 expandQS() 371 mediaHierarchyManager.setTransitionToFullShadeAmount(10000f) 372 373 val transformType = mediaHierarchyManager.calculateTransformationType() 374 assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION) 375 } 376 377 @Test calculateTransformationType_onLockSplitShade_goingToFullShade_mediaInvisible_returnsFadenull378 fun calculateTransformationType_onLockSplitShade_goingToFullShade_mediaInvisible_returnsFade() { 379 enableSplitShade() 380 goToLockscreen() 381 expandQS() 382 whenever(lockHost.visible).thenReturn(false) 383 mediaHierarchyManager.setTransitionToFullShadeAmount(10000f) 384 385 val transformType = mediaHierarchyManager.calculateTransformationType() 386 assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE) 387 } 388 389 @Test calculateTransformationType_onLockShade_inSplitShade_notExpanding_returnsFadenull390 fun calculateTransformationType_onLockShade_inSplitShade_notExpanding_returnsFade() { 391 enableSplitShade() 392 goToLockscreen() 393 goToLockedShade() 394 expandQS() 395 mediaHierarchyManager.setTransitionToFullShadeAmount(0f) 396 397 val transformType = mediaHierarchyManager.calculateTransformationType() 398 assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE) 399 } 400 401 @Test testTransformationOnLockScreenToQQSisFadingnull402 fun testTransformationOnLockScreenToQQSisFading() { 403 goToLockscreen() 404 goToLockedShade() 405 406 val transformType = mediaHierarchyManager.calculateTransformationType() 407 assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE) 408 } 409 410 @Test testCloseGutsRelayToCarouselnull411 fun testCloseGutsRelayToCarousel() { 412 mediaHierarchyManager.closeGuts() 413 414 verify(mediaCarouselController).closeGuts() 415 } 416 417 @Test testCloseGutsWhenDozenull418 fun testCloseGutsWhenDoze() { 419 statusBarCallback.value.onDozingChanged(true) 420 421 verify(mediaCarouselController).closeGuts() 422 } 423 424 @Test getGuidedTransformationTranslationY_notInGuidedTransformation_returnsNegativeNumbernull425 fun getGuidedTransformationTranslationY_notInGuidedTransformation_returnsNegativeNumber() { 426 assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY()).isLessThan(0) 427 } 428 429 @Test getGuidedTransformationTranslationY_inGuidedTransformation_returnsCurrentTranslationnull430 fun getGuidedTransformationTranslationY_inGuidedTransformation_returnsCurrentTranslation() { 431 enterGuidedTransformation() 432 433 val expectedTranslation = LOCKSCREEN_TOP - QS_TOP 434 assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY()) 435 .isEqualTo(expectedTranslation) 436 } 437 438 @Test getGuidedTransformationTranslationY_previousHostInvisible_returnsZeronull439 fun getGuidedTransformationTranslationY_previousHostInvisible_returnsZero() { 440 goToLockscreen() 441 enterGuidedTransformation() 442 whenever(lockHost.visible).thenReturn(false) 443 444 assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY()).isEqualTo(0) 445 } 446 447 @Test isCurrentlyInGuidedTransformation_hostsVisible_returnsTruenull448 fun isCurrentlyInGuidedTransformation_hostsVisible_returnsTrue() { 449 goToLockscreen() 450 enterGuidedTransformation() 451 whenever(lockHost.visible).thenReturn(true) 452 whenever(qsHost.visible).thenReturn(true) 453 whenever(qqsHost.visible).thenReturn(true) 454 455 assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isTrue() 456 } 457 458 @OptIn(ExperimentalCoroutinesApi::class) 459 @Test isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalsenull460 fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() = 461 testScope.runTest { 462 runCurrent() 463 isQsBypassingShade.value = true 464 runCurrent() 465 goToLockscreen() 466 enterGuidedTransformation() 467 whenever(lockHost.visible).thenReturn(true) 468 whenever(qsHost.visible).thenReturn(true) 469 whenever(qqsHost.visible).thenReturn(true) 470 471 assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse() 472 } 473 474 @Test isCurrentlyInGuidedTransformation_hostNotVisible_returnsFalse_with_activenull475 fun isCurrentlyInGuidedTransformation_hostNotVisible_returnsFalse_with_active() { 476 goToLockscreen() 477 enterGuidedTransformation() 478 whenever(lockHost.visible).thenReturn(false) 479 whenever(qsHost.visible).thenReturn(true) 480 whenever(qqsHost.visible).thenReturn(true) 481 whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true) 482 483 assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse() 484 } 485 486 @Test isCurrentlyInGuidedTransformation_hostNotVisible_returnsTrue_without_activenull487 fun isCurrentlyInGuidedTransformation_hostNotVisible_returnsTrue_without_active() { 488 // To keep the appearing behavior, we need to be in a guided transition 489 goToLockscreen() 490 enterGuidedTransformation() 491 whenever(lockHost.visible).thenReturn(false) 492 whenever(qsHost.visible).thenReturn(true) 493 whenever(qqsHost.visible).thenReturn(true) 494 whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false) 495 496 assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isTrue() 497 } 498 499 @Test isCurrentlyInGuidedTransformation_desiredLocationIsHub_returnsFalsenull500 fun isCurrentlyInGuidedTransformation_desiredLocationIsHub_returnsFalse() = 501 testScope.runTest { 502 goToLockscreen() 503 keyguardTransitionRepository.sendTransitionSteps( 504 from = KeyguardState.LOCKSCREEN, 505 to = KeyguardState.GLANCEABLE_HUB, 506 testScope = testScope, 507 ) 508 kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal) 509 runCurrent() 510 mediaHierarchyManager.qsExpansion = 0f 511 mediaHierarchyManager.setTransitionToFullShadeAmount(123f) 512 513 whenever(lockHost.visible).thenReturn(true) 514 whenever(qsHost.visible).thenReturn(true) 515 whenever(qqsHost.visible).thenReturn(true) 516 whenever(hubModeHost.visible).thenReturn(true) 517 518 assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse() 519 } 520 521 @Test testDreamnull522 fun testDream() { 523 goToDream() 524 setMediaDreamComplicationEnabled(true) 525 verify(mediaCarouselController) 526 .onDesiredLocationChanged( 527 eq(MediaHierarchyManager.LOCATION_DREAM_OVERLAY), 528 nullable(), 529 eq(false), 530 anyLong(), 531 anyLong() 532 ) 533 clearInvocations(mediaCarouselController) 534 535 setMediaDreamComplicationEnabled(false) 536 verify(mediaCarouselController) 537 .onDesiredLocationChanged( 538 eq(MediaHierarchyManager.LOCATION_QQS), 539 any<MediaHostState>(), 540 eq(false), 541 anyLong(), 542 anyLong() 543 ) 544 } 545 546 @Test testCommunalLocationnull547 fun testCommunalLocation() = 548 testScope.runTest { 549 keyguardTransitionRepository.sendTransitionSteps( 550 from = KeyguardState.LOCKSCREEN, 551 to = KeyguardState.GLANCEABLE_HUB, 552 testScope = testScope, 553 ) 554 kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal) 555 runCurrent() 556 verify(mediaCarouselController) 557 .onDesiredLocationChanged( 558 eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB), 559 nullable(), 560 eq(false), 561 anyLong(), 562 anyLong() 563 ) 564 clearInvocations(mediaCarouselController) 565 566 keyguardTransitionRepository.sendTransitionSteps( 567 from = KeyguardState.GLANCEABLE_HUB, 568 to = KeyguardState.LOCKSCREEN, 569 testScope = testScope, 570 ) 571 kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Blank) 572 runCurrent() 573 verify(mediaCarouselController) 574 .onDesiredLocationChanged( 575 eq(MediaHierarchyManager.LOCATION_QQS), 576 any<MediaHostState>(), 577 eq(false), 578 anyLong(), 579 anyLong() 580 ) 581 } 582 583 @Test testCommunalLocationVisibilityWithShadeShowingnull584 fun testCommunalLocationVisibilityWithShadeShowing() = 585 testScope.runTest { 586 whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true) 587 keyguardTransitionRepository.sendTransitionSteps( 588 from = KeyguardState.LOCKSCREEN, 589 to = KeyguardState.GLANCEABLE_HUB, 590 testScope = testScope, 591 ) 592 kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal) 593 runCurrent() 594 verify(mediaCarouselController) 595 .onDesiredLocationChanged( 596 eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB), 597 nullable(), 598 eq(false), 599 anyLong(), 600 anyLong() 601 ) 602 603 val captor = ArgumentCaptor.forClass(Boolean::class.java) 604 verify(mediaCarouselScrollHandler, atLeastOnce()).visibleToUser = captor.capture() 605 606 assertThat(captor.lastValue).isTrue() 607 608 clearInvocations(mediaCarouselScrollHandler) 609 anyShadeExpanded.value = true 610 runCurrent() 611 verify(mediaCarouselScrollHandler, atLeastOnce()).visibleToUser = captor.capture() 612 613 assertThat(captor.lastValue).isFalse() 614 } 615 616 @Test testCommunalLocationVisibilityWithPrimaryBouncerShowingnull617 fun testCommunalLocationVisibilityWithPrimaryBouncerShowing() = 618 testScope.runTest { 619 whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true) 620 keyguardTransitionRepository.sendTransitionSteps( 621 from = KeyguardState.LOCKSCREEN, 622 to = KeyguardState.GLANCEABLE_HUB, 623 testScope = testScope, 624 ) 625 kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal) 626 runCurrent() 627 verify(mediaCarouselController) 628 .onDesiredLocationChanged( 629 eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB), 630 nullable(), 631 eq(false), 632 anyLong(), 633 anyLong() 634 ) 635 636 val captor = ArgumentCaptor.forClass(Boolean::class.java) 637 verify(mediaCarouselScrollHandler, atLeastOnce()).visibleToUser = captor.capture() 638 639 assertThat(captor.lastValue).isTrue() 640 641 clearInvocations(mediaCarouselScrollHandler) 642 kosmos.keyguardBouncerRepository.setPrimaryShow(true) 643 runCurrent() 644 verify(mediaCarouselScrollHandler, atLeastOnce()).visibleToUser = captor.capture() 645 646 assertThat(captor.lastValue).isFalse() 647 } 648 649 @Test testCommunalLocation_showsOverLockscreennull650 fun testCommunalLocation_showsOverLockscreen() = 651 testScope.runTest { 652 // Device is on lock screen. 653 whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) 654 655 // UMO goes to communal from the lock screen. 656 keyguardTransitionRepository.sendTransitionSteps( 657 from = KeyguardState.LOCKSCREEN, 658 to = KeyguardState.GLANCEABLE_HUB, 659 testScope = testScope, 660 ) 661 kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal) 662 runCurrent() 663 verify(mediaCarouselController) 664 .onDesiredLocationChanged( 665 eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB), 666 nullable(), 667 eq(false), 668 anyLong(), 669 anyLong() 670 ) 671 } 672 673 @Test testCommunalLocation_showsUntilQsExpandsnull674 fun testCommunalLocation_showsUntilQsExpands() = 675 testScope.runTest { 676 // Device is on lock screen. 677 whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) 678 679 keyguardTransitionRepository.sendTransitionSteps( 680 from = KeyguardState.LOCKSCREEN, 681 to = KeyguardState.GLANCEABLE_HUB, 682 testScope = testScope, 683 ) 684 kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal) 685 runCurrent() 686 verify(mediaCarouselController) 687 .onDesiredLocationChanged( 688 eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB), 689 nullable(), 690 eq(false), 691 anyLong(), 692 anyLong() 693 ) 694 clearInvocations(mediaCarouselController) 695 696 // Start opening the shade. 697 mediaHierarchyManager.qsExpansion = 0.1f 698 runCurrent() 699 700 // UMO goes to the shade instead. 701 verify(mediaCarouselController) 702 .onDesiredLocationChanged( 703 eq(MediaHierarchyManager.LOCATION_QS), 704 any<MediaHostState>(), 705 eq(false), 706 anyLong(), 707 anyLong() 708 ) 709 } 710 711 @Test testCommunalLocation_whenDreamingAndShadeExpandingnull712 fun testCommunalLocation_whenDreamingAndShadeExpanding() = 713 testScope.runTest { 714 keyguardRepository.setDreaming(true) 715 runCurrent() 716 keyguardTransitionRepository.sendTransitionSteps( 717 from = KeyguardState.DREAMING, 718 to = KeyguardState.GLANCEABLE_HUB, 719 testScope = testScope, 720 ) 721 kosmos.fakeCommunalSceneRepository.changeScene(CommunalScenes.Communal) 722 runCurrent() 723 // Mock the behavior for dreaming that pulling down shade will immediately set QS as 724 // expanded 725 expandQS() 726 // Starts opening the shade 727 shadeExpansion.value = 0.1f 728 runCurrent() 729 730 // UMO shows on hub 731 verify(mediaCarouselController) 732 .onDesiredLocationChanged( 733 eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB), 734 anyOrNull(), 735 eq(false), 736 anyLong(), 737 anyLong() 738 ) 739 clearInvocations(mediaCarouselController) 740 741 // The shade is opened enough to make QS elements visible 742 shadeExpansion.value = 0.5f 743 runCurrent() 744 745 // UMO shows on QS 746 verify(mediaCarouselController) 747 .onDesiredLocationChanged( 748 eq(MediaHierarchyManager.LOCATION_QS), 749 any<MediaHostState>(), 750 eq(false), 751 anyLong(), 752 anyLong() 753 ) 754 } 755 756 @Test testQsExpandedChanged_noQqsMedianull757 fun testQsExpandedChanged_noQqsMedia() { 758 // When we are looking at QQS with active media 759 whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) 760 whenever(statusBarStateController.isExpanded).thenReturn(true) 761 762 // When there is no longer any active media 763 whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false) 764 mediaHierarchyManager.qsExpanded = false 765 766 // Then the carousel is set to not visible 767 verify(mediaCarouselScrollHandler).visibleToUser = false 768 assertThat(mediaCarouselScrollHandler.visibleToUser).isFalse() 769 } 770 enableSplitShadenull771 private fun enableSplitShade() { 772 context 773 .getOrCreateTestableResources() 774 .addOverride(R.bool.config_use_split_notification_shade, true) 775 configurationController.notifyConfigurationChanged() 776 } 777 goToLockscreennull778 private fun goToLockscreen() { 779 whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) 780 settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 1) 781 statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD) 782 whenever(dreamOverlayStateController.isOverlayActive).thenReturn(false) 783 dreamOverlayCallback.value.onStateChanged() 784 clearInvocations(mediaCarouselController) 785 } 786 goToLockedShadenull787 private fun goToLockedShade() { 788 whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) 789 statusBarCallback.value.onStatePreChange( 790 StatusBarState.KEYGUARD, 791 StatusBarState.SHADE_LOCKED 792 ) 793 } 794 goToDreamnull795 private fun goToDream() { 796 whenever(dreamOverlayStateController.isOverlayActive).thenReturn(true) 797 dreamOverlayCallback.value.onStateChanged() 798 } 799 setMediaDreamComplicationEnablednull800 private fun setMediaDreamComplicationEnabled(enabled: Boolean) { 801 val complications = if (enabled) listOf(mock<MediaDreamComplication>()) else emptyList() 802 whenever(dreamOverlayStateController.complications).thenReturn(complications) 803 dreamOverlayCallback.value.onComplicationsChanged() 804 } 805 expandQSnull806 private fun expandQS() { 807 mediaHierarchyManager.qsExpansion = 1.0f 808 } 809 enterGuidedTransformationnull810 private fun enterGuidedTransformation() { 811 mediaHierarchyManager.qsExpansion = 1.0f 812 goToLockscreen() 813 mediaHierarchyManager.setTransitionToFullShadeAmount(123f) 814 } 815 816 companion object { 817 private const val QQS_TOP = 123 818 private const val QS_TOP = 456 819 private const val LOCKSCREEN_TOP = 789 820 private const val COMMUNAL_TOP = 111 821 } 822 } 823