1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.systemui.bouncer.domain.interactor 18 19 import android.hardware.biometrics.BiometricSourceType 20 import android.testing.AndroidTestingRunner 21 import android.testing.TestableLooper.RunWithLooper 22 import android.testing.TestableResources 23 import android.view.View 24 import androidx.test.filters.SmallTest 25 import com.android.keyguard.KeyguardSecurityModel 26 import com.android.keyguard.KeyguardUpdateMonitor 27 import com.android.systemui.DejankUtils 28 import com.android.systemui.R 29 import com.android.systemui.SysuiTestCase 30 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository 31 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN 32 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE 33 import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel 34 import com.android.systemui.bouncer.ui.BouncerView 35 import com.android.systemui.bouncer.ui.BouncerViewDelegate 36 import com.android.systemui.classifier.FalsingCollector 37 import com.android.systemui.flags.FakeFeatureFlags 38 import com.android.systemui.flags.Flags 39 import com.android.systemui.keyguard.DismissCallbackRegistry 40 import com.android.systemui.keyguard.data.repository.FakeTrustRepository 41 import com.android.systemui.plugins.ActivityStarter 42 import com.android.systemui.statusbar.policy.KeyguardStateController 43 import com.android.systemui.util.mockito.any 44 import com.android.systemui.util.mockito.whenever 45 import com.android.systemui.utils.os.FakeHandler 46 import com.google.common.truth.Truth.assertThat 47 import kotlinx.coroutines.test.TestScope 48 import kotlinx.coroutines.test.runCurrent 49 import org.junit.Before 50 import org.junit.Test 51 import org.junit.runner.RunWith 52 import org.mockito.Answers 53 import org.mockito.ArgumentCaptor 54 import org.mockito.Mock 55 import org.mockito.Mockito.anyInt 56 import org.mockito.Mockito.mock 57 import org.mockito.Mockito.never 58 import org.mockito.Mockito.verify 59 import org.mockito.MockitoAnnotations 60 61 @SmallTest 62 @RunWithLooper(setAsMainLooper = true) 63 @RunWith(AndroidTestingRunner::class) 64 class PrimaryBouncerInteractorTest : SysuiTestCase() { 65 @Mock(answer = Answers.RETURNS_DEEP_STUBS) 66 private lateinit var repository: KeyguardBouncerRepository 67 @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView 68 @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate 69 @Mock private lateinit var keyguardStateController: KeyguardStateController 70 @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel 71 @Mock private lateinit var mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor 72 @Mock private lateinit var falsingCollector: FalsingCollector 73 @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry 74 @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor 75 private lateinit var mainHandler: FakeHandler 76 private lateinit var underTest: PrimaryBouncerInteractor 77 private lateinit var resources: TestableResources 78 private lateinit var trustRepository: FakeTrustRepository 79 private lateinit var featureFlags: FakeFeatureFlags 80 private lateinit var testScope: TestScope 81 82 @Before 83 fun setUp() { 84 MockitoAnnotations.initMocks(this) 85 whenever(keyguardSecurityModel.getSecurityMode(anyInt())) 86 .thenReturn(KeyguardSecurityModel.SecurityMode.PIN) 87 88 DejankUtils.setImmediate(true) 89 testScope = TestScope() 90 mainHandler = FakeHandler(android.os.Looper.getMainLooper()) 91 trustRepository = FakeTrustRepository() 92 featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) } 93 underTest = 94 PrimaryBouncerInteractor( 95 repository, 96 bouncerView, 97 mainHandler, 98 keyguardStateController, 99 keyguardSecurityModel, 100 mPrimaryBouncerCallbackInteractor, 101 falsingCollector, 102 dismissCallbackRegistry, 103 context, 104 keyguardUpdateMonitor, 105 trustRepository, 106 featureFlags, 107 testScope.backgroundScope, 108 ) 109 whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) 110 whenever(repository.primaryBouncerShow.value).thenReturn(false) 111 whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate) 112 resources = context.orCreateTestableResources 113 } 114 115 @Test 116 fun testShow_isScrimmed() { 117 underTest.show(true) 118 verify(repository).setKeyguardAuthenticated(null) 119 verify(repository).setPrimaryStartingToHide(false) 120 verify(repository).setPrimaryScrimmed(true) 121 verify(repository).setPanelExpansion(EXPANSION_VISIBLE) 122 verify(repository).setPrimaryShowingSoon(true) 123 verify(keyguardStateController).notifyPrimaryBouncerShowing(true) 124 verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToShow() 125 verify(repository).setPrimaryShow(true) 126 verify(repository).setPrimaryShowingSoon(false) 127 verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.VISIBLE) 128 } 129 130 @Test 131 fun testShow_isNotScrimmed() { 132 verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE) 133 } 134 135 @Test 136 fun testShow_keyguardIsDone() { 137 whenever(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true) 138 verify(keyguardStateController, never()).notifyPrimaryBouncerShowing(true) 139 verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow() 140 } 141 142 @Test 143 fun testShow_isResumed() { 144 whenever(repository.primaryBouncerShow.value).thenReturn(true) 145 whenever(keyguardSecurityModel.getSecurityMode(anyInt())) 146 .thenReturn(KeyguardSecurityModel.SecurityMode.SimPuk) 147 148 underTest.show(true) 149 verify(repository).setPrimaryShow(false) 150 verify(repository).setPrimaryShow(true) 151 } 152 153 @Test 154 fun testHide() { 155 underTest.hide() 156 verify(falsingCollector).onBouncerHidden() 157 verify(keyguardStateController).notifyPrimaryBouncerShowing(false) 158 verify(repository).setPrimaryShowingSoon(false) 159 verify(repository).setPrimaryShow(false) 160 verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE) 161 verify(repository).setPrimaryStartDisappearAnimation(null) 162 verify(repository).setPanelExpansion(EXPANSION_HIDDEN) 163 } 164 165 @Test 166 fun testExpansion() { 167 whenever(repository.panelExpansionAmount.value).thenReturn(0.5f) 168 underTest.setPanelExpansion(0.6f) 169 verify(repository).setPanelExpansion(0.6f) 170 verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f) 171 } 172 173 @Test 174 fun testExpansion_fullyShown() { 175 whenever(repository.panelExpansionAmount.value).thenReturn(0.5f) 176 whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) 177 underTest.setPanelExpansion(EXPANSION_VISIBLE) 178 verify(falsingCollector).onBouncerShown() 179 verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown() 180 } 181 182 @Test 183 fun testExpansion_fullyHidden() { 184 whenever(repository.panelExpansionAmount.value).thenReturn(0.5f) 185 whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) 186 underTest.setPanelExpansion(EXPANSION_HIDDEN) 187 verify(repository).setPrimaryShow(false) 188 verify(falsingCollector).onBouncerHidden() 189 verify(mPrimaryBouncerCallbackInteractor).dispatchReset() 190 verify(mPrimaryBouncerCallbackInteractor).dispatchFullyHidden() 191 } 192 193 @Test 194 fun testExpansion_startingToHide() { 195 whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE) 196 underTest.setPanelExpansion(0.1f) 197 verify(repository).setPrimaryStartingToHide(true) 198 verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide() 199 } 200 201 @Test 202 fun testShowMessage() { 203 val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java) 204 underTest.showMessage("abc", null) 205 verify(repository).setShowMessage(argCaptor.capture()) 206 assertThat(argCaptor.value.message).isEqualTo("abc") 207 } 208 209 @Test 210 fun testDismissAction() { 211 val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java) 212 val cancelAction = mock(Runnable::class.java) 213 underTest.setDismissAction(onDismissAction, cancelAction) 214 verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction) 215 } 216 217 @Test 218 fun testUpdateResources() { 219 underTest.updateResources() 220 verify(repository).setResourceUpdateRequests(true) 221 } 222 223 @Test 224 fun testNotifyKeyguardAuthenticated() { 225 underTest.notifyKeyguardAuthenticated(true) 226 verify(repository).setKeyguardAuthenticated(true) 227 } 228 229 @Test 230 fun testNotifyShowedMessage() { 231 underTest.onMessageShown() 232 verify(repository).setShowMessage(null) 233 } 234 235 @Test 236 fun testSetKeyguardPosition() { 237 underTest.setKeyguardPosition(0f) 238 verify(repository).setKeyguardPosition(0f) 239 } 240 241 @Test 242 fun testNotifyKeyguardAuthenticatedHandled() { 243 underTest.notifyKeyguardAuthenticatedHandled() 244 verify(repository).setKeyguardAuthenticated(null) 245 } 246 247 @Test 248 fun testNotifyUpdatedResources() { 249 underTest.notifyUpdatedResources() 250 verify(repository).setResourceUpdateRequests(false) 251 } 252 253 @Test 254 fun testSetBackButtonEnabled() { 255 underTest.setBackButtonEnabled(true) 256 verify(repository).setIsBackButtonEnabled(true) 257 } 258 259 @Test 260 fun testStartDisappearAnimation_willRunDismissFromKeyguard() { 261 whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(true) 262 263 val runnable = mock(Runnable::class.java) 264 underTest.startDisappearAnimation(runnable) 265 // End runnable should run immediately 266 verify(runnable).run() 267 // ... while the disappear animation should never be run 268 verify(repository, never()).setPrimaryStartDisappearAnimation(any(Runnable::class.java)) 269 } 270 271 @Test 272 fun testStartDisappearAnimation_willNotRunDismissFromKeyguard_() { 273 whenever(bouncerViewDelegate.willRunDismissFromKeyguard()).thenReturn(false) 274 275 val runnable = mock(Runnable::class.java) 276 underTest.startDisappearAnimation(runnable) 277 verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java)) 278 } 279 280 @Test 281 fun testIsFullShowing() { 282 whenever(repository.primaryBouncerShow.value).thenReturn(true) 283 whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE) 284 whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) 285 assertThat(underTest.isFullyShowing()).isTrue() 286 whenever(repository.primaryBouncerShow.value).thenReturn(false) 287 assertThat(underTest.isFullyShowing()).isFalse() 288 } 289 290 @Test 291 fun testIsScrimmed() { 292 whenever(repository.primaryBouncerScrimmed.value).thenReturn(true) 293 assertThat(underTest.isScrimmed()).isTrue() 294 whenever(repository.primaryBouncerScrimmed.value).thenReturn(false) 295 assertThat(underTest.isScrimmed()).isFalse() 296 } 297 298 @Test 299 fun testIsInTransit() { 300 whenever(repository.primaryBouncerShowingSoon.value).thenReturn(true) 301 assertThat(underTest.isInTransit()).isTrue() 302 whenever(repository.primaryBouncerShowingSoon.value).thenReturn(false) 303 assertThat(underTest.isInTransit()).isFalse() 304 whenever(repository.panelExpansionAmount.value).thenReturn(0.5f) 305 assertThat(underTest.isInTransit()).isTrue() 306 } 307 308 @Test 309 fun testIsAnimatingAway() { 310 whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {}) 311 assertThat(underTest.isAnimatingAway()).isTrue() 312 whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) 313 assertThat(underTest.isAnimatingAway()).isFalse() 314 } 315 316 @Test 317 fun testWillDismissWithAction() { 318 whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(true) 319 assertThat(underTest.willDismissWithAction()).isTrue() 320 whenever(bouncerViewDelegate.willDismissWithActions()).thenReturn(false) 321 assertThat(underTest.willDismissWithAction()).isFalse() 322 } 323 324 @Test 325 fun testSideFpsVisibility() { 326 updateSideFpsVisibilityParameters( 327 isVisible = true, 328 sfpsEnabled = true, 329 fpsDetectionRunning = true, 330 isUnlockingWithFpAllowed = true, 331 isAnimatingAway = false 332 ) 333 underTest.updateSideFpsVisibility() 334 verify(repository).setSideFpsShowing(true) 335 } 336 337 @Test 338 fun testSideFpsVisibility_notVisible() { 339 updateSideFpsVisibilityParameters( 340 isVisible = false, 341 sfpsEnabled = true, 342 fpsDetectionRunning = true, 343 isUnlockingWithFpAllowed = true, 344 isAnimatingAway = false 345 ) 346 underTest.updateSideFpsVisibility() 347 verify(repository).setSideFpsShowing(false) 348 } 349 350 @Test 351 fun testSideFpsVisibility_sfpsNotEnabled() { 352 updateSideFpsVisibilityParameters( 353 isVisible = true, 354 sfpsEnabled = false, 355 fpsDetectionRunning = true, 356 isUnlockingWithFpAllowed = true, 357 isAnimatingAway = false 358 ) 359 underTest.updateSideFpsVisibility() 360 verify(repository).setSideFpsShowing(false) 361 } 362 363 @Test 364 fun testSideFpsVisibility_fpsDetectionNotRunning() { 365 updateSideFpsVisibilityParameters( 366 isVisible = true, 367 sfpsEnabled = true, 368 fpsDetectionRunning = false, 369 isUnlockingWithFpAllowed = true, 370 isAnimatingAway = false 371 ) 372 underTest.updateSideFpsVisibility() 373 verify(repository).setSideFpsShowing(false) 374 } 375 376 @Test 377 fun testSideFpsVisibility_UnlockingWithFpNotAllowed() { 378 updateSideFpsVisibilityParameters( 379 isVisible = true, 380 sfpsEnabled = true, 381 fpsDetectionRunning = true, 382 isUnlockingWithFpAllowed = false, 383 isAnimatingAway = false 384 ) 385 underTest.updateSideFpsVisibility() 386 verify(repository).setSideFpsShowing(false) 387 } 388 389 @Test 390 fun testSideFpsVisibility_AnimatingAway() { 391 updateSideFpsVisibilityParameters( 392 isVisible = true, 393 sfpsEnabled = true, 394 fpsDetectionRunning = true, 395 isUnlockingWithFpAllowed = true, 396 isAnimatingAway = true 397 ) 398 underTest.updateSideFpsVisibility() 399 verify(repository).setSideFpsShowing(false) 400 } 401 402 @Test 403 fun delayBouncerWhenFaceAuthPossible() { 404 mainHandler.setMode(FakeHandler.Mode.QUEUEING) 405 406 // GIVEN bouncer should be delayed due to face auth 407 whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true) 408 whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)) 409 .thenReturn(true) 410 whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(true) 411 412 // WHEN bouncer show is requested 413 underTest.show(true) 414 415 // THEN primary show & primary showing soon aren't updated immediately 416 verify(repository, never()).setPrimaryShow(true) 417 verify(repository, never()).setPrimaryShowingSoon(false) 418 419 // WHEN all queued messages are dispatched 420 mainHandler.dispatchQueuedMessages() 421 422 // THEN primary show & primary showing soon are updated 423 verify(repository).setPrimaryShow(true) 424 verify(repository).setPrimaryShowingSoon(false) 425 } 426 427 @Test 428 fun noDelayBouncer_biometricsAllowed_postureDoesNotAllowFaceAuth() { 429 mainHandler.setMode(FakeHandler.Mode.QUEUEING) 430 431 // GIVEN bouncer should not be delayed because device isn't in the right posture for 432 // face auth 433 whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true) 434 whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)) 435 .thenReturn(true) 436 whenever(keyguardUpdateMonitor.doesCurrentPostureAllowFaceAuth()).thenReturn(false) 437 438 // WHEN bouncer show is requested 439 underTest.show(true) 440 441 // THEN primary show & primary showing soon are updated immediately 442 verify(repository).setPrimaryShow(true) 443 verify(repository).setPrimaryShowingSoon(false) 444 } 445 446 @Test 447 fun delayBouncerWhenActiveUnlockPossible() { 448 testScope.run { 449 mainHandler.setMode(FakeHandler.Mode.QUEUEING) 450 451 // GIVEN bouncer should be delayed due to active unlock 452 trustRepository.setCurrentUserActiveUnlockAvailable(true) 453 whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()) 454 .thenReturn(true) 455 runCurrent() 456 457 // WHEN bouncer show is requested 458 underTest.show(true) 459 460 // THEN primary show & primary showing soon were scheduled to update 461 verify(repository, never()).setPrimaryShow(true) 462 verify(repository, never()).setPrimaryShowingSoon(false) 463 464 // WHEN all queued messages are dispatched 465 mainHandler.dispatchQueuedMessages() 466 467 // THEN primary show & primary showing soon are updated 468 verify(repository).setPrimaryShow(true) 469 verify(repository).setPrimaryShowingSoon(false) 470 } 471 } 472 473 private fun updateSideFpsVisibilityParameters( 474 isVisible: Boolean, 475 sfpsEnabled: Boolean, 476 fpsDetectionRunning: Boolean, 477 isUnlockingWithFpAllowed: Boolean, 478 isAnimatingAway: Boolean 479 ) { 480 whenever(repository.primaryBouncerShow.value).thenReturn(isVisible) 481 resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled) 482 whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning) 483 .thenReturn(fpsDetectionRunning) 484 whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed) 485 .thenReturn(isUnlockingWithFpAllowed) 486 whenever(repository.primaryBouncerStartingDisappearAnimation.value) 487 .thenReturn(if (isAnimatingAway) Runnable {} else null) 488 } 489 } 490