1 /* 2 * Copyright (C) 2022 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.keyguard.ui.viewmodel 18 19 import android.app.admin.DevicePolicyManager 20 import android.content.Intent 21 import android.os.UserHandle 22 import android.platform.test.flag.junit.FlagsParameterization 23 import androidx.test.filters.SmallTest 24 import com.android.internal.logging.testing.UiEventLoggerFake 25 import com.android.internal.widget.LockPatternUtils 26 import com.android.keyguard.logging.KeyguardQuickAffordancesLogger 27 import com.android.systemui.SysuiTestCase 28 import com.android.systemui.animation.DialogTransitionAnimator 29 import com.android.systemui.animation.Expandable 30 import com.android.systemui.broadcast.BroadcastDispatcher 31 import com.android.systemui.common.shared.model.Icon 32 import com.android.systemui.coroutines.collectLastValue 33 import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor 34 import com.android.systemui.dock.DockManagerFake 35 import com.android.systemui.doze.util.BurnInHelperWrapper 36 import com.android.systemui.flags.FakeFeatureFlags 37 import com.android.systemui.flags.Flags 38 import com.android.systemui.flags.andSceneContainer 39 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys 40 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig 41 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory 42 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig 43 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer 44 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager 45 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager 46 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository 47 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository 48 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository 49 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor 50 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory 51 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor 52 import com.android.systemui.keyguard.domain.interactor.KeyguardTouchHandlingInteractor 53 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor 54 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState 55 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition 56 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger 57 import com.android.systemui.kosmos.testDispatcher 58 import com.android.systemui.kosmos.testScope 59 import com.android.systemui.plugins.ActivityStarter 60 import com.android.systemui.res.R 61 import com.android.systemui.scene.domain.interactor.sceneInteractor 62 import com.android.systemui.settings.UserFileManager 63 import com.android.systemui.settings.UserTracker 64 import com.android.systemui.shade.domain.interactor.shadeInteractor 65 import com.android.systemui.shade.pulsingGestureListener 66 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots 67 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper 68 import com.android.systemui.statusbar.policy.KeyguardStateController 69 import com.android.systemui.testKosmos 70 import com.android.systemui.util.FakeSharedPreferences 71 import com.android.systemui.util.mockito.any 72 import com.android.systemui.util.mockito.mock 73 import com.android.systemui.util.mockito.whenever 74 import com.android.systemui.util.settings.fakeSettings 75 import com.google.common.truth.Truth.assertThat 76 import kotlin.math.max 77 import kotlin.math.min 78 import kotlinx.coroutines.flow.map 79 import kotlinx.coroutines.test.runTest 80 import org.junit.Before 81 import org.junit.Test 82 import org.junit.runner.RunWith 83 import org.mockito.ArgumentMatchers.anyInt 84 import org.mockito.ArgumentMatchers.anyString 85 import org.mockito.Mock 86 import org.mockito.Mockito 87 import org.mockito.Mockito.verifyNoMoreInteractions 88 import org.mockito.MockitoAnnotations 89 import platform.test.runner.parameterized.ParameterizedAndroidJunit4 90 import platform.test.runner.parameterized.Parameters 91 92 @SmallTest 93 @RunWith(ParameterizedAndroidJunit4::class) 94 class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { 95 96 private val kosmos = testKosmos() 97 private val testDispatcher = kosmos.testDispatcher 98 private val testScope = kosmos.testScope 99 private val settings = kosmos.fakeSettings 100 101 @Mock private lateinit var expandable: Expandable 102 @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper 103 @Mock private lateinit var lockPatternUtils: LockPatternUtils 104 @Mock private lateinit var keyguardStateController: KeyguardStateController 105 @Mock private lateinit var userTracker: UserTracker 106 @Mock private lateinit var activityStarter: ActivityStarter 107 @Mock private lateinit var launchAnimator: DialogTransitionAnimator 108 @Mock private lateinit var devicePolicyManager: DevicePolicyManager 109 @Mock private lateinit var logger: KeyguardQuickAffordancesLogger 110 @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger 111 @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher 112 @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper 113 114 private lateinit var underTest: KeyguardBottomAreaViewModel 115 116 private lateinit var repository: FakeKeyguardRepository 117 private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig 118 private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig 119 private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig 120 private lateinit var dockManager: DockManagerFake 121 private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository 122 123 init { 124 mSetFlagsRule.setFlagsParameterization(flags) 125 } 126 127 @Before setUpnull128 fun setUp() { 129 MockitoAnnotations.initMocks(this) 130 131 overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true) 132 overrideResource( 133 R.array.config_keyguardQuickAffordanceDefaults, 134 arrayOf( 135 KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + 136 ":" + 137 BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS, 138 KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + 139 ":" + 140 BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET 141 ) 142 ) 143 144 whenever(burnInHelperWrapper.burnInOffset(anyInt(), any())) 145 .thenReturn(RETURNED_BURN_IN_OFFSET) 146 147 homeControlsQuickAffordanceConfig = 148 FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS) 149 quickAccessWalletAffordanceConfig = 150 FakeKeyguardQuickAffordanceConfig( 151 BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET 152 ) 153 qrCodeScannerAffordanceConfig = 154 FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER) 155 dockManager = DockManagerFake() 156 biometricSettingsRepository = FakeBiometricSettingsRepository() 157 val featureFlags = 158 FakeFeatureFlags().apply { set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) } 159 160 val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags) 161 val keyguardInteractor = withDeps.keyguardInteractor 162 repository = withDeps.repository 163 164 whenever(userTracker.userHandle).thenReturn(mock()) 165 whenever(lockPatternUtils.getStrongAuthForUser(anyInt())) 166 .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED) 167 val localUserSelectionManager = 168 KeyguardQuickAffordanceLocalUserSelectionManager( 169 context = context, 170 userFileManager = 171 mock<UserFileManager>().apply { 172 whenever( 173 getSharedPreferences( 174 anyString(), 175 anyInt(), 176 anyInt(), 177 ) 178 ) 179 .thenReturn(FakeSharedPreferences()) 180 }, 181 userTracker = userTracker, 182 broadcastDispatcher = fakeBroadcastDispatcher, 183 ) 184 val remoteUserSelectionManager = 185 KeyguardQuickAffordanceRemoteUserSelectionManager( 186 scope = testScope.backgroundScope, 187 userTracker = userTracker, 188 clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker), 189 userHandle = UserHandle.SYSTEM, 190 ) 191 val quickAffordanceRepository = 192 KeyguardQuickAffordanceRepository( 193 appContext = context, 194 scope = testScope.backgroundScope, 195 localUserSelectionManager = localUserSelectionManager, 196 remoteUserSelectionManager = remoteUserSelectionManager, 197 userTracker = userTracker, 198 legacySettingSyncer = 199 KeyguardQuickAffordanceLegacySettingSyncer( 200 scope = testScope.backgroundScope, 201 backgroundDispatcher = testDispatcher, 202 secureSettings = settings, 203 selectionsManager = localUserSelectionManager, 204 ), 205 configs = 206 setOf( 207 homeControlsQuickAffordanceConfig, 208 quickAccessWalletAffordanceConfig, 209 qrCodeScannerAffordanceConfig, 210 ), 211 dumpManager = mock(), 212 userHandle = UserHandle.SYSTEM, 213 ) 214 val keyguardTouchHandlingInteractor = 215 KeyguardTouchHandlingInteractor( 216 context = mContext, 217 scope = testScope.backgroundScope, 218 transitionInteractor = kosmos.keyguardTransitionInteractor, 219 repository = repository, 220 logger = UiEventLoggerFake(), 221 featureFlags = featureFlags, 222 broadcastDispatcher = broadcastDispatcher, 223 accessibilityManager = accessibilityManager, 224 pulsingGestureListener = kosmos.pulsingGestureListener, 225 faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor, 226 ) 227 underTest = 228 KeyguardBottomAreaViewModel( 229 keyguardInteractor = keyguardInteractor, 230 quickAffordanceInteractor = 231 KeyguardQuickAffordanceInteractor( 232 keyguardInteractor = keyguardInteractor, 233 shadeInteractor = kosmos.shadeInteractor, 234 lockPatternUtils = lockPatternUtils, 235 keyguardStateController = keyguardStateController, 236 userTracker = userTracker, 237 activityStarter = activityStarter, 238 featureFlags = featureFlags, 239 repository = { quickAffordanceRepository }, 240 launchAnimator = launchAnimator, 241 logger = logger, 242 metricsLogger = metricsLogger, 243 devicePolicyManager = devicePolicyManager, 244 dockManager = dockManager, 245 biometricSettingsRepository = biometricSettingsRepository, 246 backgroundDispatcher = testDispatcher, 247 appContext = mContext, 248 sceneInteractor = { kosmos.sceneInteractor }, 249 ), 250 bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository), 251 burnInHelperWrapper = burnInHelperWrapper, 252 keyguardTouchHandlingViewModel = 253 KeyguardTouchHandlingViewModel( 254 interactor = keyguardTouchHandlingInteractor, 255 ), 256 settingsMenuViewModel = 257 KeyguardSettingsMenuViewModel( 258 interactor = keyguardTouchHandlingInteractor, 259 ), 260 ) 261 } 262 263 @Test startButton_present_visibleModel_startsActivityOnClicknull264 fun startButton_present_visibleModel_startsActivityOnClick() = 265 testScope.runTest { 266 repository.setKeyguardShowing(true) 267 val latest = collectLastValue(underTest.startButton) 268 269 val testConfig = 270 TestConfig( 271 isVisible = true, 272 isClickable = true, 273 isActivated = true, 274 icon = mock(), 275 canShowWhileLocked = false, 276 intent = Intent("action"), 277 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), 278 ) 279 val configKey = 280 setUpQuickAffordanceModel( 281 position = KeyguardQuickAffordancePosition.BOTTOM_START, 282 testConfig = testConfig, 283 ) 284 285 assertQuickAffordanceViewModel( 286 viewModel = latest(), 287 testConfig = testConfig, 288 configKey = configKey, 289 ) 290 } 291 292 @Test startButton_hiddenWhenDevicePolicyDisablesAllKeyguardFeaturesnull293 fun startButton_hiddenWhenDevicePolicyDisablesAllKeyguardFeatures() = 294 testScope.runTest { 295 whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)) 296 .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL) 297 repository.setKeyguardShowing(true) 298 val latest by collectLastValue(underTest.startButton) 299 300 val testConfig = 301 TestConfig( 302 isVisible = true, 303 isClickable = true, 304 isActivated = true, 305 icon = mock(), 306 canShowWhileLocked = false, 307 intent = Intent("action"), 308 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), 309 ) 310 val configKey = 311 setUpQuickAffordanceModel( 312 position = KeyguardQuickAffordancePosition.BOTTOM_START, 313 testConfig = testConfig, 314 ) 315 316 assertQuickAffordanceViewModel( 317 viewModel = latest, 318 testConfig = 319 TestConfig( 320 isVisible = false, 321 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), 322 ), 323 configKey = configKey, 324 ) 325 } 326 327 @Test startButton_inPreviewMode_visibleEvenWhenKeyguardNotShowingnull328 fun startButton_inPreviewMode_visibleEvenWhenKeyguardNotShowing() = 329 testScope.runTest { 330 underTest.enablePreviewMode( 331 initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, 332 shouldHighlightSelectedAffordance = true, 333 ) 334 repository.setKeyguardShowing(false) 335 val latest = collectLastValue(underTest.startButton) 336 337 val icon: Icon = mock() 338 val configKey = 339 setUpQuickAffordanceModel( 340 position = KeyguardQuickAffordancePosition.BOTTOM_START, 341 testConfig = 342 TestConfig( 343 isVisible = true, 344 isClickable = true, 345 isActivated = true, 346 icon = icon, 347 canShowWhileLocked = false, 348 intent = Intent("action"), 349 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), 350 ), 351 ) 352 353 assertQuickAffordanceViewModel( 354 viewModel = latest(), 355 testConfig = 356 TestConfig( 357 isVisible = true, 358 isClickable = false, 359 isActivated = false, 360 icon = icon, 361 canShowWhileLocked = false, 362 intent = Intent("action"), 363 isSelected = true, 364 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), 365 ), 366 configKey = configKey, 367 ) 368 assertThat(latest()?.isSelected).isTrue() 369 } 370 371 @Test endButton_inHiglightedPreviewMode_dimmedWhenOtherIsSelectednull372 fun endButton_inHiglightedPreviewMode_dimmedWhenOtherIsSelected() = 373 testScope.runTest { 374 underTest.enablePreviewMode( 375 initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, 376 shouldHighlightSelectedAffordance = true, 377 ) 378 repository.setKeyguardShowing(false) 379 val startButton = collectLastValue(underTest.startButton) 380 val endButton = collectLastValue(underTest.endButton) 381 382 val icon: Icon = mock() 383 setUpQuickAffordanceModel( 384 position = KeyguardQuickAffordancePosition.BOTTOM_START, 385 testConfig = 386 TestConfig( 387 isVisible = true, 388 isClickable = true, 389 isActivated = true, 390 icon = icon, 391 canShowWhileLocked = false, 392 intent = Intent("action"), 393 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), 394 ), 395 ) 396 val configKey = 397 setUpQuickAffordanceModel( 398 position = KeyguardQuickAffordancePosition.BOTTOM_END, 399 testConfig = 400 TestConfig( 401 isVisible = true, 402 isClickable = true, 403 isActivated = true, 404 icon = icon, 405 canShowWhileLocked = false, 406 intent = Intent("action"), 407 slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(), 408 ), 409 ) 410 411 assertQuickAffordanceViewModel( 412 viewModel = endButton(), 413 testConfig = 414 TestConfig( 415 isVisible = true, 416 isClickable = false, 417 isActivated = false, 418 icon = icon, 419 canShowWhileLocked = false, 420 intent = Intent("action"), 421 isDimmed = true, 422 slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(), 423 ), 424 configKey = configKey, 425 ) 426 } 427 428 @Test endButton_present_visibleModel_doNothingOnClicknull429 fun endButton_present_visibleModel_doNothingOnClick() = 430 testScope.runTest { 431 repository.setKeyguardShowing(true) 432 val latest = collectLastValue(underTest.endButton) 433 434 val config = 435 TestConfig( 436 isVisible = true, 437 isClickable = true, 438 icon = mock(), 439 canShowWhileLocked = false, 440 intent = 441 null, // This will cause it to tell the system that the click was handled. 442 slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(), 443 ) 444 val configKey = 445 setUpQuickAffordanceModel( 446 position = KeyguardQuickAffordancePosition.BOTTOM_END, 447 testConfig = config, 448 ) 449 450 assertQuickAffordanceViewModel( 451 viewModel = latest(), 452 testConfig = config, 453 configKey = configKey, 454 ) 455 } 456 457 @Test startButton_notPresent_modelIsHiddennull458 fun startButton_notPresent_modelIsHidden() = 459 testScope.runTest { 460 val latest = collectLastValue(underTest.startButton) 461 462 val config = 463 TestConfig( 464 isVisible = false, 465 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), 466 ) 467 val configKey = 468 setUpQuickAffordanceModel( 469 position = KeyguardQuickAffordancePosition.BOTTOM_START, 470 testConfig = config, 471 ) 472 473 assertQuickAffordanceViewModel( 474 viewModel = latest(), 475 testConfig = config, 476 configKey = configKey, 477 ) 478 } 479 480 @Test animateButtonRevealnull481 fun animateButtonReveal() = 482 testScope.runTest { 483 repository.setKeyguardShowing(true) 484 val testConfig = 485 TestConfig( 486 isVisible = true, 487 isClickable = true, 488 icon = mock(), 489 canShowWhileLocked = false, 490 intent = Intent("action"), 491 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), 492 ) 493 494 setUpQuickAffordanceModel( 495 position = KeyguardQuickAffordancePosition.BOTTOM_START, 496 testConfig = testConfig, 497 ) 498 499 val value = collectLastValue(underTest.startButton.map { it.animateReveal }) 500 501 assertThat(value()).isFalse() 502 repository.setAnimateDozingTransitions(true) 503 assertThat(value()).isTrue() 504 repository.setAnimateDozingTransitions(false) 505 assertThat(value()).isFalse() 506 } 507 508 @Test isOverlayContainerVisiblenull509 fun isOverlayContainerVisible() = 510 testScope.runTest { 511 val value = collectLastValue(underTest.isOverlayContainerVisible) 512 513 assertThat(value()).isTrue() 514 repository.setIsDozing(true) 515 assertThat(value()).isFalse() 516 repository.setIsDozing(false) 517 assertThat(value()).isTrue() 518 } 519 520 @Test alphanull521 fun alpha() = 522 testScope.runTest { 523 val value = collectLastValue(underTest.alpha) 524 525 assertThat(value()).isEqualTo(1f) 526 repository.setBottomAreaAlpha(0.1f) 527 assertThat(value()).isEqualTo(0.1f) 528 repository.setBottomAreaAlpha(0.5f) 529 assertThat(value()).isEqualTo(0.5f) 530 repository.setBottomAreaAlpha(0.2f) 531 assertThat(value()).isEqualTo(0.2f) 532 repository.setBottomAreaAlpha(0f) 533 assertThat(value()).isEqualTo(0f) 534 } 535 536 @Test alpha_inPreviewMode_doesNotChangenull537 fun alpha_inPreviewMode_doesNotChange() = 538 testScope.runTest { 539 underTest.enablePreviewMode( 540 initiallySelectedSlotId = null, 541 shouldHighlightSelectedAffordance = false, 542 ) 543 val value = collectLastValue(underTest.alpha) 544 545 assertThat(value()).isEqualTo(1f) 546 repository.setBottomAreaAlpha(0.1f) 547 assertThat(value()).isEqualTo(1f) 548 repository.setBottomAreaAlpha(0.5f) 549 assertThat(value()).isEqualTo(1f) 550 repository.setBottomAreaAlpha(0.2f) 551 assertThat(value()).isEqualTo(1f) 552 repository.setBottomAreaAlpha(0f) 553 assertThat(value()).isEqualTo(1f) 554 } 555 556 @Test isClickable_trueWhenAlphaAtThresholdnull557 fun isClickable_trueWhenAlphaAtThreshold() = 558 testScope.runTest { 559 repository.setKeyguardShowing(true) 560 repository.setBottomAreaAlpha( 561 KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD 562 ) 563 564 val testConfig = 565 TestConfig( 566 isVisible = true, 567 isClickable = true, 568 icon = mock(), 569 canShowWhileLocked = false, 570 intent = Intent("action"), 571 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), 572 ) 573 val configKey = 574 setUpQuickAffordanceModel( 575 position = KeyguardQuickAffordancePosition.BOTTOM_START, 576 testConfig = testConfig, 577 ) 578 579 val latest = collectLastValue(underTest.startButton) 580 581 assertQuickAffordanceViewModel( 582 viewModel = latest(), 583 testConfig = testConfig, 584 configKey = configKey, 585 ) 586 } 587 588 @Test isClickable_trueWhenAlphaAboveThresholdnull589 fun isClickable_trueWhenAlphaAboveThreshold() = 590 testScope.runTest { 591 repository.setKeyguardShowing(true) 592 val latest = collectLastValue(underTest.startButton) 593 repository.setBottomAreaAlpha( 594 min(1f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f), 595 ) 596 597 val testConfig = 598 TestConfig( 599 isVisible = true, 600 isClickable = true, 601 icon = mock(), 602 canShowWhileLocked = false, 603 intent = Intent("action"), 604 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), 605 ) 606 val configKey = 607 setUpQuickAffordanceModel( 608 position = KeyguardQuickAffordancePosition.BOTTOM_START, 609 testConfig = testConfig, 610 ) 611 612 assertQuickAffordanceViewModel( 613 viewModel = latest(), 614 testConfig = testConfig, 615 configKey = configKey, 616 ) 617 } 618 619 @Test isClickable_falseWhenAlphaBelowThresholdnull620 fun isClickable_falseWhenAlphaBelowThreshold() = 621 testScope.runTest { 622 repository.setKeyguardShowing(true) 623 val latest = collectLastValue(underTest.startButton) 624 repository.setBottomAreaAlpha( 625 max(0f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f), 626 ) 627 628 val testConfig = 629 TestConfig( 630 isVisible = true, 631 isClickable = false, 632 icon = mock(), 633 canShowWhileLocked = false, 634 intent = Intent("action"), 635 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), 636 ) 637 val configKey = 638 setUpQuickAffordanceModel( 639 position = KeyguardQuickAffordancePosition.BOTTOM_START, 640 testConfig = testConfig, 641 ) 642 643 assertQuickAffordanceViewModel( 644 viewModel = latest(), 645 testConfig = testConfig, 646 configKey = configKey, 647 ) 648 } 649 650 @Test isClickable_falseWhenAlphaAtZeronull651 fun isClickable_falseWhenAlphaAtZero() = 652 testScope.runTest { 653 repository.setKeyguardShowing(true) 654 val latest = collectLastValue(underTest.startButton) 655 repository.setBottomAreaAlpha(0f) 656 657 val testConfig = 658 TestConfig( 659 isVisible = true, 660 isClickable = false, 661 icon = mock(), 662 canShowWhileLocked = false, 663 intent = Intent("action"), 664 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(), 665 ) 666 val configKey = 667 setUpQuickAffordanceModel( 668 position = KeyguardQuickAffordancePosition.BOTTOM_START, 669 testConfig = testConfig, 670 ) 671 672 assertQuickAffordanceViewModel( 673 viewModel = latest(), 674 testConfig = testConfig, 675 configKey = configKey, 676 ) 677 } 678 setUpQuickAffordanceModelnull679 private suspend fun setUpQuickAffordanceModel( 680 position: KeyguardQuickAffordancePosition, 681 testConfig: TestConfig, 682 ): String { 683 val config = 684 when (position) { 685 KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig 686 KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig 687 } 688 689 val lockScreenState = 690 if (testConfig.isVisible) { 691 if (testConfig.intent != null) { 692 config.onTriggeredResult = 693 KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity( 694 intent = testConfig.intent, 695 canShowWhileLocked = testConfig.canShowWhileLocked, 696 ) 697 } 698 KeyguardQuickAffordanceConfig.LockScreenState.Visible( 699 icon = testConfig.icon ?: error("Icon is unexpectedly null!"), 700 activationState = 701 when (testConfig.isActivated) { 702 true -> ActivationState.Active 703 false -> ActivationState.Inactive 704 } 705 ) 706 } else { 707 KeyguardQuickAffordanceConfig.LockScreenState.Hidden 708 } 709 config.setState(lockScreenState) 710 711 return "${position.toSlotId()}::${config.key}" 712 } 713 assertQuickAffordanceViewModelnull714 private fun assertQuickAffordanceViewModel( 715 viewModel: KeyguardQuickAffordanceViewModel?, 716 testConfig: TestConfig, 717 configKey: String, 718 ) { 719 checkNotNull(viewModel) 720 assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible) 721 assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable) 722 assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated) 723 assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected) 724 assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed) 725 assertThat(viewModel.slotId).isEqualTo(testConfig.slotId) 726 if (testConfig.isVisible) { 727 assertThat(viewModel.icon).isEqualTo(testConfig.icon) 728 viewModel.onClicked.invoke( 729 KeyguardQuickAffordanceViewModel.OnClickedParameters( 730 configKey = configKey, 731 expandable = expandable, 732 slotId = viewModel.slotId, 733 ) 734 ) 735 if (testConfig.intent != null) { 736 assertThat(Mockito.mockingDetails(activityStarter).invocations).hasSize(1) 737 } else { 738 verifyNoMoreInteractions(activityStarter) 739 } 740 } else { 741 assertThat(viewModel.isVisible).isFalse() 742 } 743 } 744 745 private data class TestConfig( 746 val isVisible: Boolean, 747 val isClickable: Boolean = false, 748 val isActivated: Boolean = false, 749 val icon: Icon? = null, 750 val canShowWhileLocked: Boolean = false, 751 val intent: Intent? = null, 752 val isSelected: Boolean = false, 753 val isDimmed: Boolean = false, 754 val slotId: String = "" 755 ) { 756 init { <lambda>null757 check(!isVisible || icon != null) { "Must supply non-null icon if visible!" } 758 } 759 } 760 761 companion object { 762 private const val DEFAULT_BURN_IN_OFFSET = 5 763 private const val RETURNED_BURN_IN_OFFSET = 3 764 765 @JvmStatic 766 @Parameters(name = "{0}") getParamsnull767 fun getParams(): List<FlagsParameterization> { 768 return FlagsParameterization.allCombinationsOf().andSceneContainer() 769 } 770 } 771 } 772