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 @file:OptIn(ExperimentalCoroutinesApi::class) 17 18 package com.android.keyguard 19 20 import android.content.res.Configuration 21 import android.hardware.biometrics.BiometricOverlayConstants 22 import android.media.AudioManager 23 import android.telephony.TelephonyManager 24 import android.testing.AndroidTestingRunner 25 import android.testing.TestableLooper.RunWithLooper 26 import android.testing.TestableResources 27 import android.view.Gravity 28 import android.view.LayoutInflater 29 import android.view.MotionEvent 30 import android.view.View 31 import android.view.WindowInsetsController 32 import android.widget.FrameLayout 33 import androidx.test.filters.SmallTest 34 import com.android.internal.logging.MetricsLogger 35 import com.android.internal.logging.UiEventLogger 36 import com.android.internal.widget.LockPatternUtils 37 import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback 38 import com.android.keyguard.KeyguardSecurityModel.SecurityMode 39 import com.android.systemui.R 40 import com.android.systemui.SysuiTestCase 41 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor 42 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate 43 import com.android.systemui.biometrics.SideFpsController 44 import com.android.systemui.biometrics.SideFpsUiRequestSource 45 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants 46 import com.android.systemui.classifier.FalsingA11yDelegate 47 import com.android.systemui.classifier.FalsingCollector 48 import com.android.systemui.flags.FakeFeatureFlags 49 import com.android.systemui.flags.Flags 50 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 51 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory 52 import com.android.systemui.log.SessionTracker 53 import com.android.systemui.plugins.ActivityStarter.OnDismissAction 54 import com.android.systemui.plugins.FalsingManager 55 import com.android.systemui.scene.SceneTestUtils 56 import com.android.systemui.scene.domain.interactor.SceneInteractor 57 import com.android.systemui.scene.shared.model.ObservableTransitionState 58 import com.android.systemui.scene.shared.model.SceneKey 59 import com.android.systemui.scene.shared.model.SceneModel 60 import com.android.systemui.statusbar.policy.ConfigurationController 61 import com.android.systemui.statusbar.policy.DeviceProvisionedController 62 import com.android.systemui.statusbar.policy.KeyguardStateController 63 import com.android.systemui.statusbar.policy.UserSwitcherController 64 import com.android.systemui.user.domain.interactor.UserInteractor 65 import com.android.systemui.util.kotlin.JavaAdapter 66 import com.android.systemui.util.mockito.any 67 import com.android.systemui.util.mockito.argThat 68 import com.android.systemui.util.mockito.argumentCaptor 69 import com.android.systemui.util.mockito.capture 70 import com.android.systemui.util.mockito.mock 71 import com.android.systemui.util.mockito.whenever 72 import com.android.systemui.util.settings.GlobalSettings 73 import com.google.common.truth.Truth 74 import java.util.Optional 75 import junit.framework.Assert 76 import kotlinx.coroutines.ExperimentalCoroutinesApi 77 import kotlinx.coroutines.flow.MutableStateFlow 78 import kotlinx.coroutines.flow.flowOf 79 import kotlinx.coroutines.test.runCurrent 80 import kotlinx.coroutines.test.runTest 81 import org.junit.Before 82 import org.junit.Test 83 import org.junit.runner.RunWith 84 import org.mockito.ArgumentCaptor 85 import org.mockito.ArgumentMatcher 86 import org.mockito.ArgumentMatchers.anyBoolean 87 import org.mockito.ArgumentMatchers.anyInt 88 import org.mockito.ArgumentMatchers.eq 89 import org.mockito.Captor 90 import org.mockito.Mock 91 import org.mockito.Mockito.atLeastOnce 92 import org.mockito.Mockito.clearInvocations 93 import org.mockito.Mockito.mock 94 import org.mockito.Mockito.never 95 import org.mockito.Mockito.spy 96 import org.mockito.Mockito.verify 97 import org.mockito.MockitoAnnotations 98 99 @SmallTest 100 @RunWith(AndroidTestingRunner::class) 101 @RunWithLooper 102 class KeyguardSecurityContainerControllerTest : SysuiTestCase() { 103 104 @Mock private lateinit var view: KeyguardSecurityContainer 105 @Mock 106 private lateinit var adminSecondaryLockScreenControllerFactory: 107 AdminSecondaryLockScreenController.Factory 108 @Mock 109 private lateinit var adminSecondaryLockScreenController: AdminSecondaryLockScreenController 110 @Mock private lateinit var lockPatternUtils: LockPatternUtils 111 @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor 112 @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel 113 @Mock private lateinit var metricsLogger: MetricsLogger 114 @Mock private lateinit var uiEventLogger: UiEventLogger 115 @Mock private lateinit var keyguardStateController: KeyguardStateController 116 @Mock private lateinit var inputViewController: KeyguardInputViewController<KeyguardInputView> 117 @Mock private lateinit var windowInsetsController: WindowInsetsController 118 @Mock private lateinit var securityViewFlipper: KeyguardSecurityViewFlipper 119 @Mock private lateinit var viewFlipperController: KeyguardSecurityViewFlipperController 120 @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory 121 @Mock private lateinit var keyguardMessageAreaController: KeyguardMessageAreaController<*> 122 @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea 123 @Mock private lateinit var configurationController: ConfigurationController 124 @Mock private lateinit var emergencyButtonController: EmergencyButtonController 125 @Mock private lateinit var falsingCollector: FalsingCollector 126 @Mock private lateinit var falsingManager: FalsingManager 127 @Mock private lateinit var globalSettings: GlobalSettings 128 @Mock private lateinit var userSwitcherController: UserSwitcherController 129 @Mock private lateinit var sessionTracker: SessionTracker 130 @Mock private lateinit var keyguardViewController: KeyguardViewController 131 @Mock private lateinit var sideFpsController: SideFpsController 132 @Mock private lateinit var keyguardPasswordViewControllerMock: KeyguardPasswordViewController 133 @Mock private lateinit var falsingA11yDelegate: FalsingA11yDelegate 134 @Mock private lateinit var telephonyManager: TelephonyManager 135 @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback 136 @Mock private lateinit var audioManager: AudioManager 137 @Mock private lateinit var userInteractor: UserInteractor 138 @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate 139 @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController 140 141 @Captor 142 private lateinit var swipeListenerArgumentCaptor: 143 ArgumentCaptor<KeyguardSecurityContainer.SwipeListener> 144 @Captor 145 private lateinit var onViewInflatedCallbackArgumentCaptor: 146 ArgumentCaptor<KeyguardSecurityViewFlipperController.OnViewInflatedCallback> 147 148 private lateinit var featureFlags: FakeFeatureFlags 149 private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController 150 private lateinit var keyguardPasswordView: KeyguardPasswordView 151 private lateinit var testableResources: TestableResources 152 private lateinit var sceneTestUtils: SceneTestUtils 153 private lateinit var sceneInteractor: SceneInteractor 154 private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor 155 private lateinit var authenticationInteractor: AuthenticationInteractor 156 private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState> 157 158 private lateinit var underTest: KeyguardSecurityContainerController 159 160 @Before 161 fun setUp() { 162 MockitoAnnotations.initMocks(this) 163 164 testableResources = mContext.getOrCreateTestableResources() 165 testableResources.resources.configuration.orientation = Configuration.ORIENTATION_UNDEFINED 166 whenever(view.context).thenReturn(mContext) 167 whenever(view.resources).thenReturn(testableResources.resources) 168 169 val lp = FrameLayout.LayoutParams(/* width= */ 0, /* height= */ 0) 170 lp.gravity = 0 171 whenever(view.layoutParams).thenReturn(lp) 172 173 whenever(adminSecondaryLockScreenControllerFactory.create(any())) 174 .thenReturn(adminSecondaryLockScreenController) 175 whenever(securityViewFlipper.windowInsetsController).thenReturn(windowInsetsController) 176 keyguardPasswordView = 177 spy( 178 LayoutInflater.from(mContext).inflate(R.layout.keyguard_password_view, null) 179 as KeyguardPasswordView 180 ) 181 whenever(keyguardPasswordView.rootView).thenReturn(securityViewFlipper) 182 whenever<Any?>(keyguardPasswordView.requireViewById(R.id.bouncer_message_area)) 183 .thenReturn(keyguardMessageArea) 184 whenever(messageAreaControllerFactory.create(any())) 185 .thenReturn(keyguardMessageAreaController) 186 whenever(keyguardPasswordView.windowInsetsController).thenReturn(windowInsetsController) 187 whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN) 188 whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true) 189 whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true) 190 191 featureFlags = FakeFeatureFlags() 192 featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) 193 featureFlags.set(Flags.SCENE_CONTAINER, false) 194 featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false) 195 featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) 196 197 keyguardPasswordViewController = 198 KeyguardPasswordViewController( 199 keyguardPasswordView, 200 keyguardUpdateMonitor, 201 SecurityMode.Password, 202 lockPatternUtils, 203 null, 204 messageAreaControllerFactory, 205 null, 206 null, 207 emergencyButtonController, 208 null, 209 mock(), 210 null, 211 keyguardViewController, 212 featureFlags 213 ) 214 215 whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID) 216 sceneTestUtils = SceneTestUtils(this) 217 sceneInteractor = sceneTestUtils.sceneInteractor() 218 keyguardTransitionInteractor = 219 KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope) 220 .keyguardTransitionInteractor 221 sceneTransitionStateFlow = 222 MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen)) 223 sceneInteractor.setTransitionState(sceneTransitionStateFlow) 224 authenticationInteractor = 225 sceneTestUtils.authenticationInteractor( 226 repository = sceneTestUtils.authenticationRepository(), 227 sceneInteractor = sceneInteractor 228 ) 229 230 underTest = 231 KeyguardSecurityContainerController( 232 view, 233 adminSecondaryLockScreenControllerFactory, 234 lockPatternUtils, 235 keyguardUpdateMonitor, 236 keyguardSecurityModel, 237 metricsLogger, 238 uiEventLogger, 239 keyguardStateController, 240 viewFlipperController, 241 configurationController, 242 falsingCollector, 243 falsingManager, 244 userSwitcherController, 245 featureFlags, 246 globalSettings, 247 sessionTracker, 248 Optional.of(sideFpsController), 249 falsingA11yDelegate, 250 telephonyManager, 251 viewMediatorCallback, 252 audioManager, 253 mock(), 254 mock(), 255 { JavaAdapter(sceneTestUtils.testScope.backgroundScope) }, 256 userInteractor, 257 deviceProvisionedController, 258 faceAuthAccessibilityDelegate, 259 keyguardTransitionInteractor, 260 { authenticationInteractor }, 261 ) 262 } 263 264 @Test 265 fun onInitConfiguresViewMode() { 266 underTest.onInit() 267 verify(view) 268 .initMode( 269 eq(KeyguardSecurityContainer.MODE_DEFAULT), 270 eq(globalSettings), 271 eq(falsingManager), 272 eq(userSwitcherController), 273 any(), 274 eq(falsingA11yDelegate) 275 ) 276 } 277 278 @Test 279 fun setAccessibilityDelegate() { 280 verify(view).accessibilityDelegate = eq(faceAuthAccessibilityDelegate) 281 } 282 283 @Test 284 fun showSecurityScreen_canInflateAllModes() { 285 val modes = SecurityMode.values() 286 for (mode in modes) { 287 whenever(inputViewController.securityMode).thenReturn(mode) 288 underTest.showSecurityScreen(mode) 289 if (mode == SecurityMode.Invalid) { 290 verify(viewFlipperController, never()).getSecurityView(any(), any(), any()) 291 } else { 292 verify(viewFlipperController).getSecurityView(eq(mode), any(), any()) 293 } 294 } 295 } 296 297 @Test 298 fun onResourcesUpdate_callsThroughOnRotationChange() { 299 clearInvocations(view) 300 301 // Rotation is the same, shouldn't cause an update 302 underTest.updateResources() 303 verify(view, never()) 304 .initMode( 305 eq(KeyguardSecurityContainer.MODE_DEFAULT), 306 eq(globalSettings), 307 eq(falsingManager), 308 eq(userSwitcherController), 309 any(), 310 eq(falsingA11yDelegate) 311 ) 312 313 // Update rotation. Should trigger update 314 testableResources.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE 315 underTest.updateResources() 316 verify(view) 317 .initMode( 318 eq(KeyguardSecurityContainer.MODE_DEFAULT), 319 eq(globalSettings), 320 eq(falsingManager), 321 eq(userSwitcherController), 322 any(), 323 eq(falsingA11yDelegate) 324 ) 325 } 326 327 private fun touchDown() { 328 underTest.mGlobalTouchListener.onTouchEvent( 329 MotionEvent.obtain( 330 /* downTime= */ 0, 331 /* eventTime= */ 0, 332 MotionEvent.ACTION_DOWN, 333 /* x= */ 0f, 334 /* y= */ 0f, 335 /* metaState= */ 0 336 ) 337 ) 338 } 339 340 @Test 341 fun onInterceptTap_inhibitsFalsingInSidedSecurityMode() { 342 whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false) 343 touchDown() 344 verify(falsingCollector, never()).avoidGesture() 345 whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true) 346 touchDown() 347 verify(falsingCollector).avoidGesture() 348 } 349 350 @Test 351 fun showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() { 352 testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false) 353 setupGetSecurityView(SecurityMode.Pattern) 354 underTest.showSecurityScreen(SecurityMode.Pattern) 355 verify(view) 356 .initMode( 357 eq(KeyguardSecurityContainer.MODE_DEFAULT), 358 eq(globalSettings), 359 eq(falsingManager), 360 eq(userSwitcherController), 361 any(), 362 eq(falsingA11yDelegate) 363 ) 364 } 365 366 @Test 367 fun showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() { 368 testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true) 369 setupGetSecurityView(SecurityMode.Pattern) 370 verify(view) 371 .initMode( 372 eq(KeyguardSecurityContainer.MODE_ONE_HANDED), 373 eq(globalSettings), 374 eq(falsingManager), 375 eq(userSwitcherController), 376 any(), 377 eq(falsingA11yDelegate) 378 ) 379 } 380 381 @Test 382 fun showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() { 383 testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true) 384 setupGetSecurityView(SecurityMode.Password) 385 verify(view) 386 .initMode( 387 eq(KeyguardSecurityContainer.MODE_DEFAULT), 388 eq(globalSettings), 389 eq(falsingManager), 390 eq(userSwitcherController), 391 any(), 392 eq(falsingA11yDelegate) 393 ) 394 } 395 396 @Test 397 fun addUserSwitcherCallback() { 398 val captor = ArgumentCaptor.forClass(UserSwitcherCallback::class.java) 399 setupGetSecurityView(SecurityMode.Password) 400 verify(view) 401 .initMode(anyInt(), any(), any(), any(), captor.capture(), eq(falsingA11yDelegate)) 402 captor.value.showUnlockToContinueMessage() 403 viewControllerImmediately 404 verify(keyguardPasswordViewControllerMock) 405 .showMessage( 406 /* message= */ context.getString(R.string.keyguard_unlock_to_continue), 407 /* colorState= */ null, 408 /* animated= */ true 409 ) 410 } 411 412 @Test 413 fun addUserSwitchCallback() { 414 underTest.onViewAttached() 415 verify(userSwitcherController).addUserSwitchCallback(any()) 416 underTest.onViewDetached() 417 verify(userSwitcherController).removeUserSwitchCallback(any()) 418 } 419 420 @Test 421 fun onBouncerVisibilityChanged_resetsScale() { 422 underTest.onBouncerVisibilityChanged(false) 423 verify(view).resetScale() 424 } 425 426 @Test 427 fun showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() { 428 // GIVEN the current security method is SimPin 429 whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) 430 whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) 431 .thenReturn(false) 432 underTest.showSecurityScreen(SecurityMode.SimPin) 433 434 // WHEN a request is made from the SimPin screens to show the next security method 435 whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN) 436 underTest.showNextSecurityScreenOrFinish( 437 /* authenticated= */ true, 438 TARGET_USER_ID, 439 /* bypassSecondaryLockScreen= */ true, 440 SecurityMode.SimPin 441 ) 442 443 // THEN the next security method of PIN is set, and the keyguard is not marked as done 444 verify(viewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt()) 445 verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) 446 Truth.assertThat(underTest.currentSecurityMode).isEqualTo(SecurityMode.PIN) 447 } 448 449 @Test 450 fun showNextSecurityScreenOrFinish_DeviceNotSecure() { 451 // GIVEN the current security method is SimPin 452 whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) 453 whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) 454 .thenReturn(false) 455 underTest.showSecurityScreen(SecurityMode.SimPin) 456 457 // WHEN a request is made from the SimPin screens to show the next security method 458 whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)) 459 .thenReturn(SecurityMode.None) 460 whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true) 461 underTest.showNextSecurityScreenOrFinish( 462 /* authenticated= */ true, 463 TARGET_USER_ID, 464 /* bypassSecondaryLockScreen= */ true, 465 SecurityMode.SimPin 466 ) 467 468 // THEN the next security method of None will dismiss keyguard. 469 verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) 470 } 471 472 @Test 473 fun showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() { 474 // GIVEN current security mode has been set to PIN 475 underTest.showSecurityScreen(SecurityMode.PIN) 476 477 // WHEN a request comes from SimPin to dismiss the security screens 478 val keyguardDone = 479 underTest.showNextSecurityScreenOrFinish( 480 /* authenticated= */ true, 481 TARGET_USER_ID, 482 /* bypassSecondaryLockScreen= */ true, 483 SecurityMode.SimPin 484 ) 485 486 // THEN no action has happened, which will not dismiss the security screens 487 Truth.assertThat(keyguardDone).isEqualTo(false) 488 verify(keyguardUpdateMonitor, never()).getUserHasTrust(anyInt()) 489 } 490 491 @Test 492 fun showNextSecurityScreenOrFinish_SimPin_Swipe() { 493 // GIVEN the current security method is SimPin 494 whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) 495 whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) 496 .thenReturn(false) 497 underTest.showSecurityScreen(SecurityMode.SimPin) 498 499 // WHEN a request is made from the SimPin screens to show the next security method 500 whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)) 501 .thenReturn(SecurityMode.None) 502 // WHEN security method is SWIPE 503 whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false) 504 underTest.showNextSecurityScreenOrFinish( 505 /* authenticated= */ true, 506 TARGET_USER_ID, 507 /* bypassSecondaryLockScreen= */ true, 508 SecurityMode.SimPin 509 ) 510 511 // THEN the next security method of None will dismiss keyguard. 512 verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) 513 } 514 @Test 515 fun showNextSecurityScreenOrFinish_SimPin_Swipe_userNotSetup() { 516 // GIVEN the current security method is SimPin 517 whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) 518 whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) 519 .thenReturn(false) 520 underTest.showSecurityScreen(SecurityMode.SimPin) 521 522 // WHEN a request is made from the SimPin screens to show the next security method 523 whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)) 524 .thenReturn(SecurityMode.None) 525 // WHEN security method is SWIPE 526 whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false) 527 whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false) 528 underTest.showNextSecurityScreenOrFinish( 529 /* authenticated= */ true, 530 TARGET_USER_ID, 531 /* bypassSecondaryLockScreen= */ true, 532 SecurityMode.SimPin 533 ) 534 535 // THEN the next security method of None will dismiss keyguard. 536 verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) 537 } 538 539 @Test 540 fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() { 541 val registeredSwipeListener = registeredSwipeListener 542 whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false) 543 setupGetSecurityView(SecurityMode.Password) 544 registeredSwipeListener.onSwipeUp() 545 verify(keyguardUpdateMonitor).requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER) 546 } 547 548 @Test 549 fun onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() { 550 val registeredSwipeListener = registeredSwipeListener 551 whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true) 552 registeredSwipeListener.onSwipeUp() 553 verify(keyguardUpdateMonitor, never()) 554 .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER) 555 } 556 557 @Test 558 fun onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() { 559 val registeredSwipeListener = registeredSwipeListener 560 whenever( 561 keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER) 562 ) 563 .thenReturn(true) 564 setupGetSecurityView(SecurityMode.Password) 565 clearInvocations(viewFlipperController) 566 registeredSwipeListener.onSwipeUp() 567 viewControllerImmediately 568 verify(keyguardPasswordViewControllerMock) 569 .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true) 570 } 571 572 @Test 573 fun onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() { 574 val registeredSwipeListener = registeredSwipeListener 575 whenever( 576 keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER) 577 ) 578 .thenReturn(false) 579 setupGetSecurityView(SecurityMode.Password) 580 registeredSwipeListener.onSwipeUp() 581 verify(keyguardPasswordViewControllerMock, never()) 582 .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true) 583 } 584 585 @Test 586 fun onDensityOrFontScaleChanged() { 587 val configurationListenerArgumentCaptor = 588 ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java) 589 underTest.onViewAttached() 590 verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture()) 591 clearInvocations(viewFlipperController) 592 configurationListenerArgumentCaptor.value.onDensityOrFontScaleChanged() 593 verify(viewFlipperController).clearViews() 594 verify(viewFlipperController) 595 .asynchronouslyInflateView( 596 eq(SecurityMode.PIN), 597 any(), 598 onViewInflatedCallbackArgumentCaptor.capture() 599 ) 600 onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController) 601 verify(view).onDensityOrFontScaleChanged() 602 } 603 604 @Test 605 fun onThemeChanged() { 606 val configurationListenerArgumentCaptor = 607 ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java) 608 underTest.onViewAttached() 609 verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture()) 610 configurationListenerArgumentCaptor.value.onThemeChanged() 611 verify(view).reloadColors() 612 } 613 614 @Test 615 fun onUiModeChanged() { 616 val configurationListenerArgumentCaptor = 617 ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java) 618 underTest.onViewAttached() 619 verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture()) 620 configurationListenerArgumentCaptor.value.onUiModeChanged() 621 verify(view).reloadColors() 622 } 623 @Test 624 fun onOrientationChanged_landscapeKeyguardFlagDisabled_blockReinflate() { 625 featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) 626 627 // Run onOrientationChanged 628 val configurationListenerArgumentCaptor = 629 ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java) 630 underTest.onViewAttached() 631 verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture()) 632 clearInvocations(viewFlipperController) 633 configurationListenerArgumentCaptor.value.onOrientationChanged( 634 Configuration.ORIENTATION_LANDSCAPE 635 ) 636 // Verify view is reinflated when flag is on 637 verify(viewFlipperController, never()).clearViews() 638 verify(viewFlipperController, never()) 639 .asynchronouslyInflateView( 640 eq(SecurityMode.PIN), 641 any(), 642 onViewInflatedCallbackArgumentCaptor.capture() 643 ) 644 } 645 646 @Test 647 fun onOrientationChanged_landscapeKeyguardFlagEnabled_doesReinflate() { 648 featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, true) 649 650 // Run onOrientationChanged 651 val configurationListenerArgumentCaptor = 652 ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java) 653 underTest.onViewAttached() 654 verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture()) 655 clearInvocations(viewFlipperController) 656 configurationListenerArgumentCaptor.value.onOrientationChanged( 657 Configuration.ORIENTATION_LANDSCAPE 658 ) 659 // Verify view is reinflated when flag is on 660 verify(viewFlipperController).clearViews() 661 verify(viewFlipperController) 662 .asynchronouslyInflateView( 663 eq(SecurityMode.PIN), 664 any(), 665 onViewInflatedCallbackArgumentCaptor.capture() 666 ) 667 } 668 669 @Test 670 fun hasDismissActions() { 671 Assert.assertFalse("Action not set yet", underTest.hasDismissActions()) 672 underTest.setOnDismissAction(mock(), null /* cancelAction */) 673 Assert.assertTrue("Action should exist", underTest.hasDismissActions()) 674 } 675 676 @Test 677 fun willRunDismissFromKeyguardIsTrue() { 678 val action: OnDismissAction = mock() 679 whenever(action.willRunAnimationOnKeyguard()).thenReturn(true) 680 underTest.setOnDismissAction(action, null /* cancelAction */) 681 underTest.finish(false /* strongAuth */, 0 /* currentUser */) 682 Truth.assertThat(underTest.willRunDismissFromKeyguard()).isTrue() 683 } 684 685 @Test 686 fun willRunDismissFromKeyguardIsFalse() { 687 val action: OnDismissAction = mock() 688 whenever(action.willRunAnimationOnKeyguard()).thenReturn(false) 689 underTest.setOnDismissAction(action, null /* cancelAction */) 690 underTest.finish(false /* strongAuth */, 0 /* currentUser */) 691 Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse() 692 } 693 694 @Test 695 fun willRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() { 696 underTest.setOnDismissAction(null /* action */, null /* cancelAction */) 697 underTest.finish(false /* strongAuth */, 0 /* currentUser */) 698 Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse() 699 } 700 701 @Test 702 fun onStartingToHide() { 703 underTest.onStartingToHide() 704 verify(viewFlipperController) 705 .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture()) 706 onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController) 707 verify(inputViewController).onStartingToHide() 708 } 709 710 @Test 711 fun gravityReappliedOnConfigurationChange() { 712 // Set initial gravity 713 testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER) 714 testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false) 715 716 // Kick off the initial pass... 717 underTest.onInit() 718 verify(view).layoutParams = any() 719 clearInvocations(view) 720 721 // Now simulate a config change 722 testableResources.addOverride( 723 R.integer.keyguard_host_view_gravity, 724 Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM 725 ) 726 underTest.updateResources() 727 verify(view).layoutParams = any() 728 } 729 730 @Test 731 fun gravityUsesOneHandGravityWhenApplicable() { 732 testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER) 733 testableResources.addOverride( 734 R.integer.keyguard_host_view_one_handed_gravity, 735 Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM 736 ) 737 738 // Start disabled. 739 testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false) 740 underTest.onInit() 741 verify(view).layoutParams = 742 argThat( 743 ArgumentMatcher { argument: FrameLayout.LayoutParams -> 744 argument.gravity == Gravity.CENTER 745 } 746 as ArgumentMatcher<FrameLayout.LayoutParams> 747 ) 748 clearInvocations(view) 749 750 // And enable 751 testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true) 752 underTest.updateResources() 753 verify(view).layoutParams = 754 argThat( 755 ArgumentMatcher { argument: FrameLayout.LayoutParams -> 756 argument.gravity == Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM 757 } 758 as ArgumentMatcher<FrameLayout.LayoutParams> 759 ) 760 } 761 762 @Test 763 fun updateKeyguardPositionDelegatesToSecurityContainer() { 764 underTest.updateKeyguardPosition(1.0f) 765 verify(view).updatePositionByTouchX(1.0f) 766 } 767 768 @Test 769 fun reinflateViewFlipper() { 770 val onViewInflatedCallback = KeyguardSecurityViewFlipperController.OnViewInflatedCallback {} 771 underTest.reinflateViewFlipper(onViewInflatedCallback) 772 verify(viewFlipperController).clearViews() 773 verify(viewFlipperController) 774 .asynchronouslyInflateView(any(), any(), eq(onViewInflatedCallback)) 775 } 776 777 @Test 778 fun sideFpsControllerShow() { 779 underTest.updateSideFpsVisibility(/* isVisible= */ true) 780 verify(sideFpsController) 781 .show( 782 SideFpsUiRequestSource.PRIMARY_BOUNCER, 783 BiometricOverlayConstants.REASON_AUTH_KEYGUARD 784 ) 785 } 786 787 @Test 788 fun sideFpsControllerHide() { 789 underTest.updateSideFpsVisibility(/* isVisible= */ false) 790 verify(sideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER) 791 } 792 793 @Test 794 fun setExpansion_setsAlpha() { 795 underTest.setExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) 796 verify(view).alpha = 1f 797 verify(view).translationY = 0f 798 } 799 800 @Test 801 fun dismissesKeyguard_whenSceneChangesToGone() = 802 sceneTestUtils.testScope.runTest { 803 featureFlags.set(Flags.SCENE_CONTAINER, true) 804 805 // Upon init, we have never dismisses the keyguard. 806 underTest.onInit() 807 runCurrent() 808 verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) 809 810 // Once the view is attached, we start listening but simply going to the bouncer scene 811 // is 812 // not enough to trigger a dismissal of the keyguard. 813 underTest.onViewAttached() 814 sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason") 815 sceneTransitionStateFlow.value = 816 ObservableTransitionState.Transition( 817 SceneKey.Lockscreen, 818 SceneKey.Bouncer, 819 flowOf(.5f) 820 ) 821 runCurrent() 822 sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") 823 sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) 824 runCurrent() 825 verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) 826 827 // While listening, going from the bouncer scene to the gone scene, does dismiss the 828 // keyguard. 829 sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason") 830 sceneTransitionStateFlow.value = 831 ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f)) 832 runCurrent() 833 sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") 834 sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) 835 runCurrent() 836 verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) 837 838 // While listening, moving back to the lockscreen scene does not dismiss the keyguard 839 // again. 840 clearInvocations(viewMediatorCallback) 841 sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason") 842 sceneTransitionStateFlow.value = 843 ObservableTransitionState.Transition( 844 SceneKey.Gone, 845 SceneKey.Lockscreen, 846 flowOf(.5f) 847 ) 848 runCurrent() 849 sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason") 850 sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen) 851 runCurrent() 852 verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) 853 854 // While listening, moving back to the bouncer scene does not dismiss the keyguard 855 // again. 856 clearInvocations(viewMediatorCallback) 857 sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason") 858 sceneTransitionStateFlow.value = 859 ObservableTransitionState.Transition( 860 SceneKey.Lockscreen, 861 SceneKey.Bouncer, 862 flowOf(.5f) 863 ) 864 runCurrent() 865 sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") 866 sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) 867 runCurrent() 868 verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) 869 870 // Detaching the view stops listening, so moving from the bouncer scene to the gone 871 // scene 872 // does not dismiss the keyguard while we're not listening. 873 underTest.onViewDetached() 874 sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason") 875 sceneTransitionStateFlow.value = 876 ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f)) 877 runCurrent() 878 sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") 879 sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) 880 runCurrent() 881 verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) 882 883 // While not listening, moving to the lockscreen does not dismiss the keyguard. 884 sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason") 885 sceneTransitionStateFlow.value = 886 ObservableTransitionState.Transition( 887 SceneKey.Gone, 888 SceneKey.Lockscreen, 889 flowOf(.5f) 890 ) 891 runCurrent() 892 sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason") 893 sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen) 894 runCurrent() 895 verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) 896 897 // While not listening, moving to the bouncer does not dismiss the keyguard. 898 sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason") 899 sceneTransitionStateFlow.value = 900 ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f)) 901 runCurrent() 902 sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") 903 sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer) 904 runCurrent() 905 verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()) 906 907 // Reattaching the view starts listening again so moving from the bouncer scene to the 908 // gone scene now does dismiss the keyguard again, this time from lockscreen. 909 underTest.onViewAttached() 910 sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason") 911 sceneTransitionStateFlow.value = 912 ObservableTransitionState.Transition( 913 SceneKey.Lockscreen, 914 SceneKey.Gone, 915 flowOf(.5f) 916 ) 917 runCurrent() 918 sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") 919 sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone) 920 runCurrent() 921 verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt()) 922 } 923 924 @Test 925 fun testResetUserSwitcher() { 926 val userSwitcher = mock(View::class.java) 927 whenever(view.findViewById<View>(R.id.keyguard_bouncer_user_switcher)) 928 .thenReturn(userSwitcher) 929 930 underTest.prepareToShow() 931 verify(userSwitcher).setAlpha(0f) 932 } 933 934 @Test 935 fun testOnUserSwitched() { 936 val userSwitchCallbackArgumentCaptor = 937 argumentCaptor<UserSwitcherController.UserSwitchCallback>() 938 underTest.onViewAttached() 939 verify(userSwitcherController) 940 .addUserSwitchCallback(capture(userSwitchCallbackArgumentCaptor)) 941 userSwitchCallbackArgumentCaptor.value.onUserSwitched() 942 verify(viewFlipperController).asynchronouslyInflateView(any(), any(), any()) 943 } 944 945 private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener 946 get() { 947 underTest.onViewAttached() 948 verify(view).setSwipeListener(swipeListenerArgumentCaptor.capture()) 949 return swipeListenerArgumentCaptor.value 950 } 951 952 private fun setupGetSecurityView(securityMode: SecurityMode) { 953 underTest.showSecurityScreen(securityMode) 954 viewControllerImmediately 955 } 956 957 private val viewControllerImmediately: Unit 958 get() { 959 verify(viewFlipperController, atLeastOnce()) 960 .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture()) 961 @Suppress("UNCHECKED_CAST") 962 onViewInflatedCallbackArgumentCaptor.value.onViewInflated( 963 keyguardPasswordViewControllerMock as KeyguardInputViewController<KeyguardInputView> 964 ) 965 } 966 967 companion object { 968 private const val TARGET_USER_ID = 100 969 } 970 } 971