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