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