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