1 /* <lambda>null2 * Copyright (C) 2023 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 18 @file:OptIn(ExperimentalCoroutinesApi::class) 19 20 package com.android.systemui.statusbar.notification.stack.ui.viewmodel 21 22 import android.platform.test.annotations.DisableFlags 23 import android.platform.test.annotations.EnableFlags 24 import android.platform.test.flag.junit.FlagsParameterization 25 import androidx.test.filters.SmallTest 26 import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX 27 import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT 28 import com.android.systemui.SysuiTestCase 29 import com.android.systemui.common.shared.model.NotificationContainerBounds 30 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository 31 import com.android.systemui.coroutines.collectLastValue 32 import com.android.systemui.coroutines.collectValues 33 import com.android.systemui.flags.BrokenWithSceneContainer 34 import com.android.systemui.flags.DisableSceneContainer 35 import com.android.systemui.flags.EnableSceneContainer 36 import com.android.systemui.flags.Flags 37 import com.android.systemui.flags.andSceneContainer 38 import com.android.systemui.flags.fakeFeatureFlagsClassic 39 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository 40 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository 41 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor 42 import com.android.systemui.keyguard.shared.model.BurnInModel 43 import com.android.systemui.keyguard.shared.model.KeyguardState 44 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD 45 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE 46 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN 47 import com.android.systemui.keyguard.shared.model.StatusBarState 48 import com.android.systemui.keyguard.shared.model.TransitionState 49 import com.android.systemui.keyguard.shared.model.TransitionStep 50 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel 51 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters 52 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor 53 import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel 54 import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel 55 import com.android.systemui.kosmos.testScope 56 import com.android.systemui.res.R 57 import com.android.systemui.shade.mockLargeScreenHeaderHelper 58 import com.android.systemui.shade.shadeTestUtil 59 import com.android.systemui.statusbar.notification.NotificationUtils.interpolate 60 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor 61 import com.android.systemui.testKosmos 62 import com.android.systemui.util.mockito.any 63 import com.android.systemui.util.mockito.whenever 64 import com.google.common.collect.Range 65 import com.google.common.truth.Truth.assertThat 66 import kotlinx.coroutines.ExperimentalCoroutinesApi 67 import kotlinx.coroutines.flow.MutableStateFlow 68 import kotlinx.coroutines.test.TestScope 69 import kotlinx.coroutines.test.advanceTimeBy 70 import kotlinx.coroutines.test.runCurrent 71 import kotlinx.coroutines.test.runTest 72 import org.junit.Before 73 import org.junit.Test 74 import org.junit.runner.RunWith 75 import org.mockito.Mockito.mock 76 import platform.test.runner.parameterized.ParameterizedAndroidJunit4 77 import platform.test.runner.parameterized.Parameters 78 79 @SmallTest 80 @RunWith(ParameterizedAndroidJunit4::class) 81 // SharedNotificationContainerViewModel is only bound when FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT is on 82 @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) 83 class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { 84 85 companion object { 86 @JvmStatic 87 @Parameters(name = "{0}") 88 fun getParams(): List<FlagsParameterization> { 89 return FlagsParameterization.allCombinationsOf( 90 FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, 91 ) 92 .andSceneContainer() 93 } 94 } 95 96 init { 97 mSetFlagsRule.setFlagsParameterization(flags) 98 } 99 100 val aodBurnInViewModel = mock(AodBurnInViewModel::class.java) 101 lateinit var movementFlow: MutableStateFlow<BurnInModel> 102 103 val kosmos = 104 testKosmos().apply { 105 fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } 106 } 107 108 init { 109 kosmos.aodBurnInViewModel = aodBurnInViewModel 110 } 111 112 val testScope = kosmos.testScope 113 val configurationRepository 114 get() = kosmos.fakeConfigurationRepository 115 116 val keyguardRepository 117 get() = kosmos.fakeKeyguardRepository 118 119 val keyguardInteractor 120 get() = kosmos.keyguardInteractor 121 122 val keyguardRootViewModel 123 get() = kosmos.keyguardRootViewModel 124 125 val keyguardTransitionRepository 126 get() = kosmos.fakeKeyguardTransitionRepository 127 128 val shadeTestUtil 129 get() = kosmos.shadeTestUtil 130 131 val sharedNotificationContainerInteractor 132 get() = kosmos.sharedNotificationContainerInteractor 133 134 val largeScreenHeaderHelper 135 get() = kosmos.mockLargeScreenHeaderHelper 136 137 lateinit var underTest: SharedNotificationContainerViewModel 138 139 @Before 140 fun setUp() { 141 overrideResource(R.bool.config_use_split_notification_shade, false) 142 movementFlow = MutableStateFlow(BurnInModel()) 143 whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow) 144 underTest = kosmos.sharedNotificationContainerViewModel 145 } 146 147 @Test 148 fun validateMarginStartInSplitShade() = 149 testScope.runTest { 150 overrideResource(R.bool.config_use_split_notification_shade, true) 151 overrideResource(R.dimen.notification_panel_margin_horizontal, 20) 152 153 val dimens by collectLastValue(underTest.configurationBasedDimensions) 154 155 configurationRepository.onAnyConfigurationChange() 156 157 assertThat(dimens!!.marginStart).isEqualTo(0) 158 } 159 160 @Test 161 fun validateMarginStart() = 162 testScope.runTest { 163 overrideResource(R.bool.config_use_split_notification_shade, false) 164 overrideResource(R.dimen.notification_panel_margin_horizontal, 20) 165 166 val dimens by collectLastValue(underTest.configurationBasedDimensions) 167 168 configurationRepository.onAnyConfigurationChange() 169 170 assertThat(dimens!!.marginStart).isEqualTo(20) 171 } 172 173 @Test 174 @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) 175 fun validatePaddingTopInSplitShade_refactorFlagOff_usesLargeHeaderResource() = 176 testScope.runTest { 177 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) 178 overrideResource(R.bool.config_use_split_notification_shade, true) 179 overrideResource(R.bool.config_use_large_screen_shade_header, true) 180 overrideResource(R.dimen.large_screen_shade_header_height, 10) 181 overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) 182 183 val paddingTop by collectLastValue(underTest.paddingTopDimen) 184 185 configurationRepository.onAnyConfigurationChange() 186 187 // Should directly use the header height (flagged off value) 188 assertThat(paddingTop).isEqualTo(10) 189 } 190 191 @Test 192 @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) 193 fun validatePaddingTopInSplitShade_refactorFlagOn_usesLargeHeaderHelper() = 194 testScope.runTest { 195 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) 196 overrideResource(R.bool.config_use_split_notification_shade, true) 197 overrideResource(R.bool.config_use_large_screen_shade_header, true) 198 overrideResource(R.dimen.large_screen_shade_header_height, 10) 199 overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) 200 201 val paddingTop by collectLastValue(underTest.paddingTopDimen) 202 configurationRepository.onAnyConfigurationChange() 203 204 // Should directly use the header height (flagged on value) 205 assertThat(paddingTop).isEqualTo(5) 206 } 207 208 @Test 209 fun validatePaddingTop() = 210 testScope.runTest { 211 overrideResource(R.bool.config_use_split_notification_shade, false) 212 overrideResource(R.dimen.large_screen_shade_header_height, 10) 213 overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) 214 215 val paddingTop by collectLastValue(underTest.paddingTopDimen) 216 217 configurationRepository.onAnyConfigurationChange() 218 219 assertThat(paddingTop).isEqualTo(0) 220 } 221 222 @Test 223 fun validateMarginEnd() = 224 testScope.runTest { 225 overrideResource(R.dimen.notification_panel_margin_horizontal, 50) 226 227 val dimens by collectLastValue(underTest.configurationBasedDimensions) 228 229 configurationRepository.onAnyConfigurationChange() 230 231 assertThat(dimens!!.marginEnd).isEqualTo(50) 232 } 233 234 @Test 235 fun validateMarginBottom() = 236 testScope.runTest { 237 overrideResource(R.dimen.notification_panel_margin_bottom, 50) 238 239 val dimens by collectLastValue(underTest.configurationBasedDimensions) 240 241 configurationRepository.onAnyConfigurationChange() 242 243 assertThat(dimens!!.marginBottom).isEqualTo(50) 244 } 245 246 @Test 247 @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) 248 @DisableSceneContainer 249 fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_usesResource() = 250 testScope.runTest { 251 val headerResourceHeight = 50 252 val headerHelperHeight = 100 253 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) 254 .thenReturn(headerHelperHeight) 255 overrideResource(R.bool.config_use_large_screen_shade_header, true) 256 overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight) 257 overrideResource(R.dimen.notification_panel_margin_top, 0) 258 259 val dimens by collectLastValue(underTest.configurationBasedDimensions) 260 261 configurationRepository.onAnyConfigurationChange() 262 263 assertThat(dimens!!.marginTop).isEqualTo(headerResourceHeight) 264 } 265 266 @Test 267 @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) 268 @EnableSceneContainer 269 fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_sceneContainerFlagOn_stillZero() = 270 testScope.runTest { 271 val headerResourceHeight = 50 272 val headerHelperHeight = 100 273 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) 274 .thenReturn(headerHelperHeight) 275 overrideResource(R.bool.config_use_large_screen_shade_header, true) 276 overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight) 277 overrideResource(R.dimen.notification_panel_margin_top, 0) 278 279 val dimens by collectLastValue(underTest.configurationBasedDimensions) 280 281 configurationRepository.onAnyConfigurationChange() 282 283 assertThat(dimens!!.marginTop).isEqualTo(0) 284 } 285 286 @Test 287 @DisableSceneContainer 288 @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) 289 fun validateMarginTopWithLargeScreenHeader_refactorFlagOn_usesHelper() = 290 testScope.runTest { 291 val headerResourceHeight = 50 292 val headerHelperHeight = 100 293 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) 294 .thenReturn(headerHelperHeight) 295 overrideResource(R.bool.config_use_large_screen_shade_header, true) 296 overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight) 297 overrideResource(R.dimen.notification_panel_margin_top, 0) 298 299 val dimens by collectLastValue(underTest.configurationBasedDimensions) 300 301 configurationRepository.onAnyConfigurationChange() 302 303 assertThat(dimens!!.marginTop).isEqualTo(headerHelperHeight) 304 } 305 306 @Test 307 @EnableSceneContainer 308 @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) 309 fun validateMarginTopWithLargeScreenHeader_sceneContainerFlagOn_stillZero() = 310 testScope.runTest { 311 val headerResourceHeight = 50 312 val headerHelperHeight = 100 313 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) 314 .thenReturn(headerHelperHeight) 315 overrideResource(R.bool.config_use_large_screen_shade_header, true) 316 overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight) 317 overrideResource(R.dimen.notification_panel_margin_top, 0) 318 319 val dimens by collectLastValue(underTest.configurationBasedDimensions) 320 321 configurationRepository.onAnyConfigurationChange() 322 323 assertThat(dimens!!.marginTop).isEqualTo(0) 324 } 325 326 @Test 327 fun glanceableHubAlpha_lockscreenToHub() = 328 testScope.runTest { 329 val alpha by collectLastValue(underTest.glanceableHubAlpha) 330 331 // Start on lockscreen 332 showLockscreen() 333 assertThat(alpha).isEqualTo(1f) 334 335 // Start transitioning to glanceable hub 336 val progress = 0.6f 337 keyguardTransitionRepository.sendTransitionStep( 338 TransitionStep( 339 transitionState = TransitionState.STARTED, 340 from = KeyguardState.LOCKSCREEN, 341 to = KeyguardState.GLANCEABLE_HUB, 342 value = 0f, 343 ) 344 ) 345 runCurrent() 346 keyguardTransitionRepository.sendTransitionStep( 347 TransitionStep( 348 transitionState = TransitionState.RUNNING, 349 from = KeyguardState.LOCKSCREEN, 350 to = KeyguardState.GLANCEABLE_HUB, 351 value = progress, 352 ) 353 ) 354 runCurrent() 355 assertThat(alpha).isIn(Range.closed(0f, 1f)) 356 357 // Finish transition to glanceable hub 358 keyguardTransitionRepository.sendTransitionStep( 359 TransitionStep( 360 transitionState = TransitionState.FINISHED, 361 from = KeyguardState.LOCKSCREEN, 362 to = KeyguardState.GLANCEABLE_HUB, 363 value = 1f, 364 ) 365 ) 366 assertThat(alpha).isEqualTo(0f) 367 368 // While state is GLANCEABLE_HUB, verify alpha is restored to full if glanceable hub is 369 // not fully visible. 370 shadeTestUtil.setLockscreenShadeExpansion(0.1f) 371 assertThat(alpha).isEqualTo(1f) 372 } 373 374 @Test 375 fun glanceableHubAlpha_dreamToHub() = 376 testScope.runTest { 377 val alpha by collectLastValue(underTest.glanceableHubAlpha) 378 379 // Start on lockscreen, notifications should be unhidden. 380 showLockscreen() 381 assertThat(alpha).isEqualTo(1f) 382 383 // Transition to dream, notifications should be hidden so that transition 384 // from dream->hub doesn't cause notification flicker. 385 showDream() 386 assertThat(alpha).isEqualTo(0f) 387 388 // Start transitioning to glanceable hub 389 val progress = 0.6f 390 keyguardTransitionRepository.sendTransitionStep( 391 TransitionStep( 392 transitionState = TransitionState.STARTED, 393 from = KeyguardState.DREAMING, 394 to = KeyguardState.GLANCEABLE_HUB, 395 value = 0f, 396 ) 397 ) 398 runCurrent() 399 keyguardTransitionRepository.sendTransitionStep( 400 TransitionStep( 401 transitionState = TransitionState.RUNNING, 402 from = KeyguardState.DREAMING, 403 to = KeyguardState.GLANCEABLE_HUB, 404 value = progress, 405 ) 406 ) 407 runCurrent() 408 // Keep notifications hidden during the transition from dream to hub 409 assertThat(alpha).isEqualTo(0) 410 411 // Finish transition to glanceable hub 412 keyguardTransitionRepository.sendTransitionStep( 413 TransitionStep( 414 transitionState = TransitionState.FINISHED, 415 from = KeyguardState.DREAMING, 416 to = KeyguardState.GLANCEABLE_HUB, 417 value = 1f, 418 ) 419 ) 420 assertThat(alpha).isEqualTo(0f) 421 } 422 423 @Test 424 fun validateMarginTop() = 425 testScope.runTest { 426 overrideResource(R.bool.config_use_large_screen_shade_header, false) 427 overrideResource(R.dimen.large_screen_shade_header_height, 50) 428 overrideResource(R.dimen.notification_panel_margin_top, 0) 429 430 val dimens by collectLastValue(underTest.configurationBasedDimensions) 431 432 configurationRepository.onAnyConfigurationChange() 433 434 assertThat(dimens!!.marginTop).isEqualTo(0) 435 } 436 437 @Test 438 fun isOnLockscreen() = 439 testScope.runTest { 440 val isOnLockscreen by collectLastValue(underTest.isOnLockscreen) 441 442 keyguardTransitionRepository.sendTransitionSteps( 443 from = KeyguardState.LOCKSCREEN, 444 to = KeyguardState.GONE, 445 testScope, 446 ) 447 assertThat(isOnLockscreen).isFalse() 448 449 // While progressing from lockscreen, should still be true 450 keyguardTransitionRepository.sendTransitionStep( 451 TransitionStep( 452 from = KeyguardState.LOCKSCREEN, 453 to = KeyguardState.GONE, 454 value = 0.8f, 455 transitionState = TransitionState.RUNNING 456 ) 457 ) 458 assertThat(isOnLockscreen).isTrue() 459 460 keyguardTransitionRepository.sendTransitionSteps( 461 from = KeyguardState.GONE, 462 to = KeyguardState.LOCKSCREEN, 463 testScope, 464 ) 465 assertThat(isOnLockscreen).isTrue() 466 467 keyguardTransitionRepository.sendTransitionSteps( 468 from = KeyguardState.LOCKSCREEN, 469 to = KeyguardState.PRIMARY_BOUNCER, 470 testScope, 471 ) 472 assertThat(isOnLockscreen).isTrue() 473 } 474 475 @Test 476 fun isOnLockscreenWithoutShade() = 477 testScope.runTest { 478 val isOnLockscreenWithoutShade by collectLastValue(underTest.isOnLockscreenWithoutShade) 479 480 // First on AOD 481 shadeTestUtil.setLockscreenShadeExpansion(0f) 482 shadeTestUtil.setQsExpansion(0f) 483 keyguardTransitionRepository.sendTransitionSteps( 484 from = KeyguardState.LOCKSCREEN, 485 to = KeyguardState.OCCLUDED, 486 testScope, 487 ) 488 assertThat(isOnLockscreenWithoutShade).isFalse() 489 490 // Now move to lockscreen 491 showLockscreen() 492 493 // While state is LOCKSCREEN, validate variations of both shade and qs expansion 494 shadeTestUtil.setQsExpansion(0f) 495 shadeTestUtil.setLockscreenShadeExpansion(0.1f) 496 assertThat(isOnLockscreenWithoutShade).isFalse() 497 498 shadeTestUtil.setLockscreenShadeExpansion(0.1f) 499 shadeTestUtil.setShadeAndQsExpansion(0.1f, .9f) 500 assertThat(isOnLockscreenWithoutShade).isFalse() 501 502 shadeTestUtil.setLockscreenShadeExpansion(0f) 503 shadeTestUtil.setQsExpansion(0.1f) 504 assertThat(isOnLockscreenWithoutShade).isFalse() 505 506 shadeTestUtil.setQsExpansion(0f) 507 shadeTestUtil.setLockscreenShadeExpansion(0f) 508 assertThat(isOnLockscreenWithoutShade).isTrue() 509 } 510 511 @Test 512 fun isOnGlanceableHubWithoutShade() = 513 testScope.runTest { 514 val isOnGlanceableHubWithoutShade by 515 collectLastValue(underTest.isOnGlanceableHubWithoutShade) 516 517 // Start on lockscreen 518 showLockscreen() 519 assertThat(isOnGlanceableHubWithoutShade).isFalse() 520 521 // Move to glanceable hub 522 keyguardTransitionRepository.sendTransitionSteps( 523 from = KeyguardState.LOCKSCREEN, 524 to = KeyguardState.GLANCEABLE_HUB, 525 testScope = this 526 ) 527 assertThat(isOnGlanceableHubWithoutShade).isTrue() 528 529 // While state is GLANCEABLE_HUB, validate variations of both shade and qs expansion 530 shadeTestUtil.setQsExpansion(0f) 531 shadeTestUtil.setLockscreenShadeExpansion(0.1f) 532 assertThat(isOnGlanceableHubWithoutShade).isFalse() 533 534 shadeTestUtil.setLockscreenShadeExpansion(0.1f) 535 shadeTestUtil.setShadeAndQsExpansion(0.1f, .9f) 536 assertThat(isOnGlanceableHubWithoutShade).isFalse() 537 538 shadeTestUtil.setLockscreenShadeExpansion(0f) 539 shadeTestUtil.setQsExpansion(0.1f) 540 assertThat(isOnGlanceableHubWithoutShade).isFalse() 541 542 shadeTestUtil.setQsExpansion(0f) 543 shadeTestUtil.setLockscreenShadeExpansion(0f) 544 assertThat(isOnGlanceableHubWithoutShade).isTrue() 545 } 546 547 @Test 548 @DisableSceneContainer 549 fun boundsOnLockscreenNotInSplitShade() = 550 testScope.runTest { 551 val bounds by collectLastValue(underTest.bounds) 552 553 // When not in split shade 554 overrideResource(R.bool.config_use_split_notification_shade, false) 555 configurationRepository.onAnyConfigurationChange() 556 runCurrent() 557 558 // Start on lockscreen 559 showLockscreen() 560 561 keyguardInteractor.setNotificationContainerBounds( 562 NotificationContainerBounds(top = 1f, bottom = 2f) 563 ) 564 565 assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 2f)) 566 } 567 568 @Test 569 @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) 570 @DisableSceneContainer 571 fun boundsOnLockscreenInSplitShade_refactorFlagOff_usesLargeHeaderResource() = 572 testScope.runTest { 573 val bounds by collectLastValue(underTest.bounds) 574 575 // When in split shade 576 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) 577 overrideResource(R.bool.config_use_split_notification_shade, true) 578 overrideResource(R.bool.config_use_large_screen_shade_header, true) 579 overrideResource(R.dimen.large_screen_shade_header_height, 10) 580 overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) 581 582 configurationRepository.onAnyConfigurationChange() 583 runCurrent() 584 585 // Start on lockscreen 586 showLockscreen() 587 588 keyguardInteractor.setNotificationContainerBounds( 589 NotificationContainerBounds(top = 1f, bottom = 52f) 590 ) 591 runCurrent() 592 593 // Top should be equal to bounds (1) - padding adjustment (10) 594 assertThat(bounds) 595 .isEqualTo( 596 NotificationContainerBounds( 597 top = -9f, 598 bottom = 2f, 599 ) 600 ) 601 } 602 603 @Test 604 @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) 605 @DisableSceneContainer 606 fun boundsOnLockscreenInSplitShade_refactorFlagOn_usesLargeHeaderHelper() = 607 testScope.runTest { 608 val bounds by collectLastValue(underTest.bounds) 609 610 // When in split shade 611 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) 612 overrideResource(R.bool.config_use_split_notification_shade, true) 613 overrideResource(R.bool.config_use_large_screen_shade_header, true) 614 overrideResource(R.dimen.large_screen_shade_header_height, 10) 615 overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) 616 617 configurationRepository.onAnyConfigurationChange() 618 runCurrent() 619 620 // Start on lockscreen 621 showLockscreen() 622 623 keyguardInteractor.setNotificationContainerBounds( 624 NotificationContainerBounds(top = 1f, bottom = 52f) 625 ) 626 runCurrent() 627 628 // Top should be equal to bounds (1) - padding adjustment (5) 629 assertThat(bounds).isEqualTo(NotificationContainerBounds(top = -4f, bottom = 2f)) 630 } 631 632 @Test 633 @DisableSceneContainer 634 fun boundsOnShade() = 635 testScope.runTest { 636 val bounds by collectLastValue(underTest.bounds) 637 638 // Start on lockscreen with shade expanded 639 showLockscreenWithShadeExpanded() 640 641 // When not in split shade 642 sharedNotificationContainerInteractor.setTopPosition(10f) 643 644 assertThat(bounds) 645 .isEqualTo(NotificationContainerBounds(top = 10f, bottom = 0f, isAnimated = true)) 646 } 647 648 @Test 649 @DisableSceneContainer 650 fun boundsOnQS() = 651 testScope.runTest { 652 val bounds by collectLastValue(underTest.bounds) 653 654 // Start on lockscreen with shade expanded 655 showLockscreenWithQSExpanded() 656 657 // When not in split shade 658 sharedNotificationContainerInteractor.setTopPosition(10f) 659 660 assertThat(bounds) 661 .isEqualTo(NotificationContainerBounds(top = 10f, bottom = 0f, isAnimated = false)) 662 } 663 664 @Test 665 fun maxNotificationsOnLockscreen() = 666 testScope.runTest { 667 var notificationCount = 10 668 val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount } 669 val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace)) 670 advanceTimeBy(50L) 671 showLockscreen() 672 673 overrideResource(R.bool.config_use_split_notification_shade, false) 674 configurationRepository.onAnyConfigurationChange() 675 676 assertThat(maxNotifications).isEqualTo(10) 677 678 // Also updates when directly requested (as it would from NotificationStackScrollLayout) 679 notificationCount = 25 680 sharedNotificationContainerInteractor.notificationStackChanged() 681 advanceTimeBy(50L) 682 assertThat(maxNotifications).isEqualTo(25) 683 684 // Also ensure another collection starts with the same value. As an example, folding 685 // then unfolding will restart the coroutine and it must get the last value immediately. 686 val newMaxNotifications by 687 collectLastValue(underTest.getMaxNotifications(calculateSpace)) 688 advanceTimeBy(50L) 689 assertThat(newMaxNotifications).isEqualTo(25) 690 } 691 692 @Test 693 fun maxNotificationsOnLockscreen_DoesNotUpdateWhenUserInteracting() = 694 testScope.runTest { 695 var notificationCount = 10 696 val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount } 697 val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace)) 698 advanceTimeBy(50L) 699 showLockscreen() 700 701 overrideResource(R.bool.config_use_split_notification_shade, false) 702 configurationRepository.onAnyConfigurationChange() 703 704 assertThat(maxNotifications).isEqualTo(10) 705 706 // Shade expanding... still 10 707 shadeTestUtil.setLockscreenShadeExpansion(0.5f) 708 assertThat(maxNotifications).isEqualTo(10) 709 710 notificationCount = 25 711 712 // When shade is expanding by user interaction 713 shadeTestUtil.setLockscreenShadeTracking(true) 714 715 // Should still be 10, since the user is interacting 716 assertThat(maxNotifications).isEqualTo(10) 717 718 shadeTestUtil.setLockscreenShadeTracking(false) 719 shadeTestUtil.setLockscreenShadeExpansion(0f) 720 721 // Stopped tracking, show 25 722 assertThat(maxNotifications).isEqualTo(25) 723 } 724 725 @Test 726 fun maxNotificationsOnShade() = 727 testScope.runTest { 728 val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> 10 } 729 val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace)) 730 advanceTimeBy(50L) 731 732 // Show lockscreen with shade expanded 733 showLockscreenWithShadeExpanded() 734 735 overrideResource(R.bool.config_use_split_notification_shade, false) 736 configurationRepository.onAnyConfigurationChange() 737 738 // -1 means No Limit 739 assertThat(maxNotifications).isEqualTo(-1) 740 } 741 742 @Test 743 @DisableSceneContainer 744 fun translationYUpdatesOnKeyguardForBurnIn() = 745 testScope.runTest { 746 val translationY by collectLastValue(underTest.translationY(BurnInParameters())) 747 748 showLockscreen() 749 assertThat(translationY).isEqualTo(0) 750 751 movementFlow.value = BurnInModel(translationY = 150) 752 assertThat(translationY).isEqualTo(150f) 753 } 754 755 @Test 756 @DisableSceneContainer 757 fun translationYUpdatesOnKeyguard() = 758 testScope.runTest { 759 val translationY by collectLastValue(underTest.translationY(BurnInParameters())) 760 761 configurationRepository.setDimensionPixelSize( 762 R.dimen.keyguard_translate_distance_on_swipe_up, 763 -100 764 ) 765 configurationRepository.onAnyConfigurationChange() 766 767 // legacy expansion means the user is swiping up, usually for the bouncer 768 shadeTestUtil.setShadeExpansion(0.5f) 769 770 showLockscreen() 771 772 // The translation values are negative 773 assertThat(translationY).isLessThan(0f) 774 } 775 776 @Test 777 @DisableSceneContainer 778 fun translationYDoesNotUpdateWhenShadeIsExpanded() = 779 testScope.runTest { 780 val translationY by collectLastValue(underTest.translationY(BurnInParameters())) 781 782 configurationRepository.setDimensionPixelSize( 783 R.dimen.keyguard_translate_distance_on_swipe_up, 784 -100 785 ) 786 configurationRepository.onAnyConfigurationChange() 787 788 // legacy expansion means the user is swiping up, usually for the bouncer but also for 789 // shade collapsing 790 shadeTestUtil.setShadeExpansion(0.5f) 791 792 showLockscreenWithShadeExpanded() 793 794 assertThat(translationY).isEqualTo(0f) 795 } 796 797 @Test 798 @DisableSceneContainer 799 fun updateBounds_fromKeyguardRoot() = 800 testScope.runTest { 801 val startProgress = 0f 802 val startStep = TransitionStep(LOCKSCREEN, AOD, startProgress, TransitionState.STARTED) 803 val boundsChangingProgress = 0.2f 804 val boundsChangingStep = 805 TransitionStep(LOCKSCREEN, AOD, boundsChangingProgress, TransitionState.RUNNING) 806 val boundsInterpolatingProgress = 0.6f 807 val boundsInterpolatingStep = 808 TransitionStep( 809 LOCKSCREEN, 810 AOD, 811 boundsInterpolatingProgress, 812 TransitionState.RUNNING 813 ) 814 val finishProgress = 1.0f 815 val finishStep = 816 TransitionStep(LOCKSCREEN, AOD, finishProgress, TransitionState.FINISHED) 817 818 val bounds by collectLastValue(underTest.bounds) 819 val top = 123f 820 val bottom = 456f 821 822 kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(startStep) 823 runCurrent() 824 kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsChangingStep) 825 runCurrent() 826 keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom) 827 828 kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsInterpolatingStep) 829 runCurrent() 830 val adjustedProgress = 831 (boundsInterpolatingProgress - boundsChangingProgress) / 832 (1 - boundsChangingProgress) 833 val interpolatedTop = interpolate(0f, top, adjustedProgress) 834 val interpolatedBottom = interpolate(0f, bottom, adjustedProgress) 835 assertThat(bounds) 836 .isEqualTo( 837 NotificationContainerBounds(top = interpolatedTop, bottom = interpolatedBottom) 838 ) 839 840 kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep) 841 runCurrent() 842 assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom)) 843 } 844 845 @Test 846 @DisableSceneContainer 847 fun updateBounds_fromGone_withoutTransitions() = 848 testScope.runTest { 849 // Start step is already at 1.0 850 val runningStep = TransitionStep(GONE, AOD, 1.0f, TransitionState.RUNNING) 851 val finishStep = TransitionStep(GONE, AOD, 1.0f, TransitionState.FINISHED) 852 853 val bounds by collectLastValue(underTest.bounds) 854 val top = 123f 855 val bottom = 456f 856 857 kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(runningStep) 858 runCurrent() 859 kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep) 860 runCurrent() 861 keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom) 862 runCurrent() 863 864 assertThat(bounds).isEqualTo( 865 NotificationContainerBounds(top = top, bottom = bottom) 866 ) 867 } 868 869 @Test 870 fun alphaOnFullQsExpansion() = 871 testScope.runTest { 872 val viewState = ViewStateAccessor() 873 val alpha by collectLastValue(underTest.keyguardAlpha(viewState)) 874 875 showLockscreenWithQSExpanded() 876 877 // Alpha fades out as QS expands 878 shadeTestUtil.setQsExpansion(0.5f) 879 assertThat(alpha).isWithin(0.01f).of(0.5f) 880 shadeTestUtil.setQsExpansion(0.9f) 881 assertThat(alpha).isWithin(0.01f).of(0.1f) 882 883 // Ensure that alpha is set back to 1f when QS is fully expanded 884 shadeTestUtil.setQsExpansion(1f) 885 assertThat(alpha).isEqualTo(1f) 886 } 887 888 @Test 889 @BrokenWithSceneContainer(330311871) 890 fun alphaDoesNotUpdateWhileGoneTransitionIsRunning() = 891 testScope.runTest { 892 val viewState = ViewStateAccessor() 893 val alpha by collectLastValue(underTest.keyguardAlpha(viewState)) 894 895 showLockscreen() 896 // GONE transition gets to 90% complete 897 keyguardTransitionRepository.sendTransitionStep( 898 TransitionStep( 899 from = KeyguardState.LOCKSCREEN, 900 to = KeyguardState.GONE, 901 transitionState = TransitionState.STARTED, 902 value = 0f, 903 ) 904 ) 905 runCurrent() 906 keyguardTransitionRepository.sendTransitionStep( 907 TransitionStep( 908 from = KeyguardState.LOCKSCREEN, 909 to = KeyguardState.GONE, 910 transitionState = TransitionState.RUNNING, 911 value = 0.9f, 912 ) 913 ) 914 runCurrent() 915 916 // At this point, alpha should be zero 917 assertThat(alpha).isEqualTo(0f) 918 919 // An attempt to override by the shade should be ignored 920 shadeTestUtil.setQsExpansion(0.5f) 921 assertThat(alpha).isEqualTo(0f) 922 } 923 924 @Test 925 fun alphaDoesNotUpdateWhileOcclusionTransitionIsRunning() = 926 testScope.runTest { 927 val viewState = ViewStateAccessor() 928 val alpha by collectLastValue(underTest.keyguardAlpha(viewState)) 929 930 showLockscreen() 931 // OCCLUDED transition gets to 90% complete 932 keyguardTransitionRepository.sendTransitionStep( 933 TransitionStep( 934 from = KeyguardState.LOCKSCREEN, 935 to = KeyguardState.OCCLUDED, 936 transitionState = TransitionState.STARTED, 937 value = 0f, 938 ) 939 ) 940 runCurrent() 941 keyguardTransitionRepository.sendTransitionStep( 942 TransitionStep( 943 from = KeyguardState.LOCKSCREEN, 944 to = KeyguardState.OCCLUDED, 945 transitionState = TransitionState.RUNNING, 946 value = 0.9f, 947 ) 948 ) 949 runCurrent() 950 951 // At this point, alpha should be zero 952 assertThat(alpha).isEqualTo(0f) 953 954 // An attempt to override by the shade should be ignored 955 shadeTestUtil.setQsExpansion(0.5f) 956 assertThat(alpha).isEqualTo(0f) 957 } 958 959 @Test 960 fun alphaWhenGoneIsSetToOne() = 961 testScope.runTest { 962 val viewState = ViewStateAccessor() 963 val alpha by collectLastValue(underTest.keyguardAlpha(viewState)) 964 965 showLockscreen() 966 967 keyguardTransitionRepository.sendTransitionSteps( 968 from = KeyguardState.LOCKSCREEN, 969 to = KeyguardState.GONE, 970 testScope 971 ) 972 keyguardRepository.setStatusBarState(StatusBarState.SHADE) 973 974 assertThat(alpha).isEqualTo(1f) 975 } 976 977 @Test 978 fun shadeCollapseFadeIn() = 979 testScope.runTest { 980 val fadeIn by collectValues(underTest.shadeCollapseFadeIn) 981 982 // Start on lockscreen without the shade 983 showLockscreen() 984 assertThat(fadeIn[0]).isEqualTo(false) 985 986 // ... then the shade expands 987 showLockscreenWithShadeExpanded() 988 assertThat(fadeIn[0]).isEqualTo(false) 989 990 // ... it collapses 991 showLockscreen() 992 assertThat(fadeIn[1]).isEqualTo(true) 993 994 // ... and ensure the value goes back to false 995 assertThat(fadeIn[2]).isEqualTo(false) 996 } 997 998 @Test 999 fun shadeCollapseFadeIn_doesNotRunIfTransitioningToAod() = 1000 testScope.runTest { 1001 val fadeIn by collectValues(underTest.shadeCollapseFadeIn) 1002 1003 // Start on lockscreen without the shade 1004 showLockscreen() 1005 assertThat(fadeIn[0]).isEqualTo(false) 1006 1007 // ... then the shade expands 1008 showLockscreenWithShadeExpanded() 1009 assertThat(fadeIn[0]).isEqualTo(false) 1010 1011 // ... then user hits power to go to AOD 1012 keyguardTransitionRepository.sendTransitionSteps( 1013 from = KeyguardState.LOCKSCREEN, 1014 to = KeyguardState.AOD, 1015 testScope, 1016 ) 1017 // ... followed by a shade collapse 1018 showLockscreen() 1019 // ... does not trigger a fade in 1020 assertThat(fadeIn[0]).isEqualTo(false) 1021 } 1022 1023 private suspend fun TestScope.showLockscreen() { 1024 shadeTestUtil.setQsExpansion(0f) 1025 shadeTestUtil.setLockscreenShadeExpansion(0f) 1026 runCurrent() 1027 keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) 1028 runCurrent() 1029 keyguardTransitionRepository.sendTransitionSteps( 1030 from = KeyguardState.AOD, 1031 to = KeyguardState.LOCKSCREEN, 1032 testScope, 1033 ) 1034 } 1035 1036 private suspend fun TestScope.showDream() { 1037 shadeTestUtil.setQsExpansion(0f) 1038 shadeTestUtil.setLockscreenShadeExpansion(0f) 1039 runCurrent() 1040 keyguardRepository.setDreaming(true) 1041 runCurrent() 1042 keyguardTransitionRepository.sendTransitionSteps( 1043 from = KeyguardState.LOCKSCREEN, 1044 to = KeyguardState.DREAMING, 1045 testScope, 1046 ) 1047 } 1048 1049 private suspend fun TestScope.showLockscreenWithShadeExpanded() { 1050 shadeTestUtil.setQsExpansion(0f) 1051 shadeTestUtil.setLockscreenShadeExpansion(1f) 1052 runCurrent() 1053 keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) 1054 runCurrent() 1055 keyguardTransitionRepository.sendTransitionSteps( 1056 from = KeyguardState.AOD, 1057 to = KeyguardState.LOCKSCREEN, 1058 testScope, 1059 ) 1060 } 1061 1062 private suspend fun TestScope.showLockscreenWithQSExpanded() { 1063 shadeTestUtil.setLockscreenShadeExpansion(0f) 1064 shadeTestUtil.setQsExpansion(1f) 1065 runCurrent() 1066 keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) 1067 runCurrent() 1068 keyguardTransitionRepository.sendTransitionSteps( 1069 from = KeyguardState.AOD, 1070 to = KeyguardState.LOCKSCREEN, 1071 testScope, 1072 ) 1073 } 1074 } 1075