1 /* 2 * Copyright (C) 2021 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.biometrics 18 19 import android.animation.Animator 20 import android.app.ActivityManager 21 import android.app.ActivityTaskManager 22 import android.content.ComponentName 23 import android.graphics.Insets 24 import android.graphics.Rect 25 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD 26 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS 27 import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN 28 import android.hardware.biometrics.SensorLocationInternal 29 import android.hardware.biometrics.SensorProperties 30 import android.hardware.display.DisplayManager 31 import android.hardware.display.DisplayManagerGlobal 32 import android.hardware.fingerprint.FingerprintManager 33 import android.hardware.fingerprint.FingerprintSensorProperties 34 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal 35 import android.hardware.fingerprint.ISidefpsController 36 import android.os.Handler 37 import android.testing.TestableLooper 38 import android.view.Display 39 import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS 40 import android.view.DisplayInfo 41 import android.view.LayoutInflater 42 import android.view.Surface 43 import android.view.View 44 import android.view.ViewPropertyAnimator 45 import android.view.WindowInsets 46 import android.view.WindowManager 47 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION 48 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY 49 import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG 50 import android.view.WindowMetrics 51 import androidx.test.ext.junit.runners.AndroidJUnit4 52 import androidx.test.filters.SmallTest 53 import com.airbnb.lottie.LottieAnimationView 54 import com.android.keyguard.KeyguardUpdateMonitor 55 import com.android.systemui.R 56 import com.android.systemui.RoboPilotTest 57 import com.android.systemui.SysuiTestCase 58 import com.android.systemui.SysuiTestableContext 59 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository 60 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor 61 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl 62 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository 63 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor 64 import com.android.systemui.dump.DumpManager 65 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository 66 import com.android.systemui.plugins.statusbar.StatusBarStateController 67 import com.android.systemui.statusbar.policy.KeyguardStateController 68 import com.android.systemui.util.concurrency.FakeExecutor 69 import com.android.systemui.util.time.FakeSystemClock 70 import com.google.common.truth.Truth.assertThat 71 import kotlinx.coroutines.test.StandardTestDispatcher 72 import kotlinx.coroutines.test.TestCoroutineScope 73 import kotlinx.coroutines.test.TestScope 74 import org.junit.Before 75 import org.junit.Rule 76 import org.junit.Test 77 import org.junit.runner.RunWith 78 import org.mockito.ArgumentCaptor 79 import org.mockito.ArgumentMatchers.eq 80 import org.mockito.Captor 81 import org.mockito.Mock 82 import org.mockito.Mockito.any 83 import org.mockito.Mockito.anyFloat 84 import org.mockito.Mockito.anyInt 85 import org.mockito.Mockito.anyLong 86 import org.mockito.Mockito.mock 87 import org.mockito.Mockito.never 88 import org.mockito.Mockito.reset 89 import org.mockito.Mockito.times 90 import org.mockito.Mockito.verify 91 import org.mockito.Mockito.`when` as whenEver 92 import org.mockito.junit.MockitoJUnit 93 94 private const val DISPLAY_ID = 2 95 private const val SENSOR_ID = 1 96 97 private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3 98 99 @SmallTest 100 @RoboPilotTest 101 @RunWith(AndroidJUnit4::class) 102 @TestableLooper.RunWithLooper 103 class SideFpsControllerTest : SysuiTestCase() { 104 105 @JvmField @Rule var rule = MockitoJUnit.rule() 106 107 @Mock lateinit var layoutInflater: LayoutInflater 108 @Mock lateinit var fingerprintManager: FingerprintManager 109 @Mock lateinit var windowManager: WindowManager 110 @Mock lateinit var activityTaskManager: ActivityTaskManager 111 @Mock lateinit var sideFpsView: View 112 @Mock lateinit var displayManager: DisplayManager 113 @Mock lateinit var handler: Handler 114 @Mock lateinit var dumpManager: DumpManager 115 @Captor lateinit var overlayCaptor: ArgumentCaptor<View> 116 @Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> 117 118 private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository 119 private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor 120 private lateinit var displayStateInteractor: DisplayStateInteractor 121 122 private val executor = FakeExecutor(FakeSystemClock()) 123 private val displayStateRepository = FakeDisplayStateRepository() 124 private val testScope = TestScope(StandardTestDispatcher()) 125 126 private lateinit var overlayController: ISidefpsController 127 private lateinit var sideFpsController: SideFpsController 128 129 enum class DeviceConfig { 130 X_ALIGNED, 131 Y_ALIGNED, 132 } 133 134 private lateinit var deviceConfig: DeviceConfig 135 private lateinit var indicatorBounds: Rect 136 private lateinit var displayBounds: Rect 137 private lateinit var sensorLocation: SensorLocationInternal 138 private var displayWidth: Int = 0 139 private var displayHeight: Int = 0 140 private var boundsWidth: Int = 0 141 private var boundsHeight: Int = 0 142 143 @Before 144 fun setup() { 145 keyguardBouncerRepository = FakeKeyguardBouncerRepository() 146 alternateBouncerInteractor = 147 AlternateBouncerInteractor( 148 mock(StatusBarStateController::class.java), 149 mock(KeyguardStateController::class.java), 150 keyguardBouncerRepository, 151 FakeBiometricSettingsRepository(), 152 FakeSystemClock(), 153 mock(KeyguardUpdateMonitor::class.java), 154 ) 155 displayStateInteractor = 156 DisplayStateInteractorImpl( 157 testScope.backgroundScope, 158 context, 159 executor, 160 displayStateRepository 161 ) 162 163 context.addMockSystemService(DisplayManager::class.java, displayManager) 164 context.addMockSystemService(WindowManager::class.java, windowManager) 165 166 whenEver(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView) 167 whenEver(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation))) 168 .thenReturn(mock(LottieAnimationView::class.java)) 169 with(mock(ViewPropertyAnimator::class.java)) { 170 whenEver(sideFpsView.animate()).thenReturn(this) 171 whenEver(alpha(anyFloat())).thenReturn(this) 172 whenEver(setStartDelay(anyLong())).thenReturn(this) 173 whenEver(setDuration(anyLong())).thenReturn(this) 174 whenEver(setListener(any())).thenAnswer { 175 (it.arguments[0] as Animator.AnimatorListener).onAnimationEnd( 176 mock(Animator::class.java) 177 ) 178 this 179 } 180 } 181 } 182 183 private fun testWithDisplay( 184 deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED, 185 isReverseDefaultRotation: Boolean = false, 186 initInfo: DisplayInfo.() -> Unit = {}, 187 windowInsets: WindowInsets = insetsForSmallNavbar(), 188 inRearDisplayMode: Boolean = false, 189 block: () -> Unit 190 ) { 191 this.deviceConfig = deviceConfig 192 193 when (deviceConfig) { 194 DeviceConfig.X_ALIGNED -> { 195 displayWidth = 3000 196 displayHeight = 1500 197 sensorLocation = SensorLocationInternal("", 2500, 0, 0) 198 boundsWidth = 200 199 boundsHeight = 100 200 } 201 DeviceConfig.Y_ALIGNED -> { 202 displayWidth = 2500 203 displayHeight = 2000 204 sensorLocation = SensorLocationInternal("", 0, 300, 0) 205 boundsWidth = 100 206 boundsHeight = 200 207 } 208 } 209 210 indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight) 211 displayBounds = Rect(0, 0, displayWidth, displayHeight) 212 var locations = listOf(sensorLocation) 213 214 whenEver(fingerprintManager.sensorPropertiesInternal) 215 .thenReturn( 216 listOf( 217 FingerprintSensorPropertiesInternal( 218 SENSOR_ID, 219 SensorProperties.STRENGTH_STRONG, 220 5 /* maxEnrollmentsPerUser */, 221 listOf() /* componentInfo */, 222 FingerprintSensorProperties.TYPE_POWER_BUTTON, 223 true /* halControlsIllumination */, 224 true /* resetLockoutRequiresHardwareAuthToken */, 225 locations 226 ) 227 ) 228 ) 229 230 val displayInfo = DisplayInfo() 231 displayInfo.initInfo() 232 233 val dmGlobal = mock(DisplayManagerGlobal::class.java) 234 val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS) 235 236 whenEver(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo) 237 whenEver(windowManager.defaultDisplay).thenReturn(display) 238 whenEver(windowManager.maximumWindowMetrics) 239 .thenReturn(WindowMetrics(displayBounds, WindowInsets.CONSUMED)) 240 whenEver(windowManager.currentWindowMetrics) 241 .thenReturn(WindowMetrics(displayBounds, windowInsets)) 242 243 val sideFpsControllerContext = context.createDisplayContext(display) as SysuiTestableContext 244 sideFpsControllerContext.orCreateTestableResources.addOverride( 245 com.android.internal.R.bool.config_reverseDefaultRotation, 246 isReverseDefaultRotation 247 ) 248 249 val rearDisplayDeviceStates = 250 if (inRearDisplayMode) intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE) else intArrayOf() 251 sideFpsControllerContext.orCreateTestableResources.addOverride( 252 com.android.internal.R.array.config_rearDisplayDeviceStates, 253 rearDisplayDeviceStates 254 ) 255 256 sideFpsController = 257 SideFpsController( 258 sideFpsControllerContext, 259 layoutInflater, 260 fingerprintManager, 261 windowManager, 262 activityTaskManager, 263 displayManager, 264 displayStateInteractor, 265 executor, 266 handler, 267 alternateBouncerInteractor, 268 TestCoroutineScope(), 269 dumpManager 270 ) 271 displayStateRepository.setIsInRearDisplayMode(inRearDisplayMode) 272 273 overlayController = 274 ArgumentCaptor.forClass(ISidefpsController::class.java) 275 .apply { verify(fingerprintManager).setSidefpsController(capture()) } 276 .value 277 278 block() 279 } 280 281 @Test 282 fun testSubscribesToOrientationChangesWhenShowingOverlay() = testWithDisplay { 283 overlayController.show(SENSOR_ID, REASON_UNKNOWN) 284 executor.runAllReady() 285 286 verify(displayManager).registerDisplayListener(any(), eq(handler), anyLong()) 287 288 overlayController.hide(SENSOR_ID) 289 executor.runAllReady() 290 verify(displayManager).unregisterDisplayListener(any()) 291 } 292 293 @Test 294 fun testShowOverlayReasonWhenDisplayChanged() = testWithDisplay { 295 sideFpsController.show(SideFpsUiRequestSource.AUTO_SHOW, REASON_AUTH_KEYGUARD) 296 executor.runAllReady() 297 sideFpsController.orientationListener.onDisplayChanged(1 /* displayId */) 298 executor.runAllReady() 299 300 assertThat(sideFpsController.orientationReasonListener.reason) 301 .isEqualTo(REASON_AUTH_KEYGUARD) 302 } 303 304 @Test 305 fun testShowsAndHides() = testWithDisplay { 306 overlayController.show(SENSOR_ID, REASON_UNKNOWN) 307 executor.runAllReady() 308 309 verify(windowManager).addView(overlayCaptor.capture(), any()) 310 311 reset(windowManager) 312 overlayController.hide(SENSOR_ID) 313 executor.runAllReady() 314 315 verify(windowManager, never()).addView(any(), any()) 316 verify(windowManager).removeView(eq(overlayCaptor.value)) 317 } 318 319 @Test 320 fun testShowsOnce() = testWithDisplay { 321 repeat(5) { 322 overlayController.show(SENSOR_ID, REASON_UNKNOWN) 323 executor.runAllReady() 324 } 325 326 verify(windowManager).addView(any(), any()) 327 verify(windowManager, never()).removeView(any()) 328 } 329 330 @Test 331 fun testHidesOnce() = testWithDisplay { 332 overlayController.show(SENSOR_ID, REASON_UNKNOWN) 333 executor.runAllReady() 334 335 repeat(5) { 336 overlayController.hide(SENSOR_ID) 337 executor.runAllReady() 338 } 339 340 verify(windowManager).addView(any(), any()) 341 verify(windowManager).removeView(any()) 342 } 343 344 @Test fun testIgnoredForKeyguard() = testWithDisplay { testIgnoredFor(REASON_AUTH_KEYGUARD) } 345 346 @Test 347 fun testShowsForMostSettings() = testWithDisplay { 348 whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpEnrollTask())) 349 testIgnoredFor(REASON_AUTH_SETTINGS, ignored = false) 350 } 351 352 @Test 353 fun testIgnoredForVerySpecificSettings() = testWithDisplay { 354 whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpSettingsTask())) 355 testIgnoredFor(REASON_AUTH_SETTINGS) 356 } 357 358 private fun testIgnoredFor(reason: Int, ignored: Boolean = true) { 359 overlayController.show(SENSOR_ID, reason) 360 executor.runAllReady() 361 362 verify(windowManager, if (ignored) never() else times(1)).addView(any(), any()) 363 } 364 365 @Test 366 fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_0() = 367 testWithDisplay( 368 deviceConfig = DeviceConfig.X_ALIGNED, 369 isReverseDefaultRotation = false, 370 { rotation = Surface.ROTATION_0 } 371 ) { 372 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 373 } 374 375 @Test 376 fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_90() = 377 testWithDisplay( 378 deviceConfig = DeviceConfig.X_ALIGNED, 379 isReverseDefaultRotation = false, 380 { rotation = Surface.ROTATION_90 } 381 ) { 382 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 383 } 384 385 @Test 386 fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_180() = 387 testWithDisplay( 388 deviceConfig = DeviceConfig.X_ALIGNED, 389 isReverseDefaultRotation = false, 390 { rotation = Surface.ROTATION_180 } 391 ) { 392 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 393 } 394 395 @Test 396 fun showsSfpsIndicatorWithTaskbarCollapsedDownForXAlignedSensor_180() = 397 testWithDisplay( 398 deviceConfig = DeviceConfig.X_ALIGNED, 399 isReverseDefaultRotation = false, 400 { rotation = Surface.ROTATION_180 }, 401 windowInsets = insetsForSmallNavbar() 402 ) { 403 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 404 } 405 406 @Test 407 fun hidesSfpsIndicatorWhenOccludingTaskbarForXAlignedSensor_180() = 408 testWithDisplay( 409 deviceConfig = DeviceConfig.X_ALIGNED, 410 isReverseDefaultRotation = false, 411 { rotation = Surface.ROTATION_180 }, 412 windowInsets = insetsForLargeNavbar() 413 ) { 414 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = false) 415 } 416 417 @Test 418 fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_270() = 419 testWithDisplay( 420 deviceConfig = DeviceConfig.X_ALIGNED, 421 isReverseDefaultRotation = false, 422 { rotation = Surface.ROTATION_270 } 423 ) { 424 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 425 } 426 427 @Test 428 fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_InReverseDefaultRotation_0() = 429 testWithDisplay( 430 deviceConfig = DeviceConfig.X_ALIGNED, 431 isReverseDefaultRotation = true, 432 { rotation = Surface.ROTATION_0 } 433 ) { 434 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 435 } 436 437 @Test 438 fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_InReverseDefaultRotation_90() = 439 testWithDisplay( 440 deviceConfig = DeviceConfig.X_ALIGNED, 441 isReverseDefaultRotation = true, 442 { rotation = Surface.ROTATION_90 } 443 ) { 444 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 445 } 446 447 @Test 448 fun showsSfpsIndicatorWithTaskbarCollapsedDownForXAlignedSensor_InReverseDefaultRotation_90() = 449 testWithDisplay( 450 deviceConfig = DeviceConfig.X_ALIGNED, 451 isReverseDefaultRotation = true, 452 { rotation = Surface.ROTATION_90 }, 453 windowInsets = insetsForSmallNavbar() 454 ) { 455 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 456 } 457 458 @Test 459 fun hidesSfpsIndicatorWhenOccludingTaskbarForXAlignedSensor_InReverseDefaultRotation_90() = 460 testWithDisplay( 461 deviceConfig = DeviceConfig.X_ALIGNED, 462 isReverseDefaultRotation = true, 463 { rotation = Surface.ROTATION_90 }, 464 windowInsets = insetsForLargeNavbar() 465 ) { 466 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = false) 467 } 468 469 @Test 470 fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_InReverseDefaultRotation_180() = 471 testWithDisplay( 472 deviceConfig = DeviceConfig.X_ALIGNED, 473 isReverseDefaultRotation = true, 474 { rotation = Surface.ROTATION_180 } 475 ) { 476 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 477 } 478 479 @Test 480 fun showsSfpsIndicatorWithTaskbarForXAlignedSensor_InReverseDefaultRotation_270() = 481 testWithDisplay( 482 deviceConfig = DeviceConfig.X_ALIGNED, 483 isReverseDefaultRotation = true, 484 { rotation = Surface.ROTATION_270 } 485 ) { 486 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 487 } 488 489 @Test 490 fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_0() = 491 testWithDisplay( 492 deviceConfig = DeviceConfig.Y_ALIGNED, 493 isReverseDefaultRotation = false, 494 { rotation = Surface.ROTATION_0 } 495 ) { 496 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 497 } 498 499 @Test 500 fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_90() = 501 testWithDisplay( 502 deviceConfig = DeviceConfig.Y_ALIGNED, 503 isReverseDefaultRotation = false, 504 { rotation = Surface.ROTATION_90 } 505 ) { 506 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 507 } 508 509 @Test 510 fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_180() = 511 testWithDisplay( 512 deviceConfig = DeviceConfig.Y_ALIGNED, 513 isReverseDefaultRotation = false, 514 { rotation = Surface.ROTATION_180 }, 515 ) { 516 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 517 } 518 519 @Test 520 fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_270() = 521 testWithDisplay( 522 deviceConfig = DeviceConfig.Y_ALIGNED, 523 isReverseDefaultRotation = false, 524 { rotation = Surface.ROTATION_270 } 525 ) { 526 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 527 } 528 529 @Test 530 fun showsSfpsIndicatorWithTaskbarCollapsedDownForYAlignedSensor_270() = 531 testWithDisplay( 532 deviceConfig = DeviceConfig.Y_ALIGNED, 533 isReverseDefaultRotation = false, 534 { rotation = Surface.ROTATION_270 }, 535 windowInsets = insetsForSmallNavbar() 536 ) { 537 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 538 } 539 540 @Test 541 fun hidesSfpsIndicatorWhenOccludingTaskbarForYAlignedSensor_270() = 542 testWithDisplay( 543 deviceConfig = DeviceConfig.Y_ALIGNED, 544 isReverseDefaultRotation = false, 545 { rotation = Surface.ROTATION_270 }, 546 windowInsets = insetsForLargeNavbar() 547 ) { 548 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = false) 549 } 550 551 @Test 552 fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_InReverseDefaultRotation_0() = 553 testWithDisplay( 554 deviceConfig = DeviceConfig.Y_ALIGNED, 555 isReverseDefaultRotation = true, 556 { rotation = Surface.ROTATION_0 } 557 ) { 558 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 559 } 560 561 @Test 562 fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_InReverseDefaultRotation_90() = 563 testWithDisplay( 564 deviceConfig = DeviceConfig.Y_ALIGNED, 565 isReverseDefaultRotation = true, 566 { rotation = Surface.ROTATION_90 }, 567 ) { 568 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 569 } 570 571 @Test 572 fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_InReverseDefaultRotation_180() = 573 testWithDisplay( 574 deviceConfig = DeviceConfig.Y_ALIGNED, 575 isReverseDefaultRotation = true, 576 { rotation = Surface.ROTATION_180 } 577 ) { 578 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 579 } 580 581 @Test 582 fun showsSfpsIndicatorWithTaskbarCollapsedDownForYAlignedSensor_InReverseDefaultRotation_180() = 583 testWithDisplay( 584 deviceConfig = DeviceConfig.Y_ALIGNED, 585 isReverseDefaultRotation = true, 586 { rotation = Surface.ROTATION_180 }, 587 windowInsets = insetsForSmallNavbar() 588 ) { 589 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 590 } 591 592 @Test 593 fun hidesSfpsIndicatorWhenOccludingTaskbarForYAlignedSensor_InReverseDefaultRotation_180() = 594 testWithDisplay( 595 deviceConfig = DeviceConfig.Y_ALIGNED, 596 isReverseDefaultRotation = true, 597 { rotation = Surface.ROTATION_180 }, 598 windowInsets = insetsForLargeNavbar() 599 ) { 600 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = false) 601 } 602 603 @Test 604 fun showsSfpsIndicatorWithTaskbarForYAlignedSensor_InReverseDefaultRotation_270() = 605 testWithDisplay( 606 deviceConfig = DeviceConfig.Y_ALIGNED, 607 isReverseDefaultRotation = true, 608 { rotation = Surface.ROTATION_270 } 609 ) { 610 verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true) 611 } 612 613 @Test 614 fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_0() = 615 testWithDisplay( 616 deviceConfig = DeviceConfig.Y_ALIGNED, 617 isReverseDefaultRotation = false, 618 { rotation = Surface.ROTATION_0 }, 619 inRearDisplayMode = true, 620 ) { 621 verifySfpsIndicator_notAdded_InRearDisplayMode() 622 } 623 624 @Test 625 fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_90() = 626 testWithDisplay( 627 deviceConfig = DeviceConfig.Y_ALIGNED, 628 isReverseDefaultRotation = false, 629 { rotation = Surface.ROTATION_90 }, 630 inRearDisplayMode = true, 631 ) { 632 verifySfpsIndicator_notAdded_InRearDisplayMode() 633 } 634 635 @Test 636 fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_180() = 637 testWithDisplay( 638 deviceConfig = DeviceConfig.Y_ALIGNED, 639 isReverseDefaultRotation = false, 640 { rotation = Surface.ROTATION_180 }, 641 inRearDisplayMode = true, 642 ) { 643 verifySfpsIndicator_notAdded_InRearDisplayMode() 644 } 645 646 @Test 647 fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_270() = 648 testWithDisplay( 649 deviceConfig = DeviceConfig.Y_ALIGNED, 650 isReverseDefaultRotation = false, 651 { rotation = Surface.ROTATION_270 }, 652 inRearDisplayMode = true, 653 ) { 654 verifySfpsIndicator_notAdded_InRearDisplayMode() 655 } 656 657 private fun verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible: Boolean) { 658 sideFpsController.overlayOffsets = sensorLocation 659 } 660 661 private fun verifySfpsIndicator_notAdded_InRearDisplayMode() { 662 sideFpsController.overlayOffsets = sensorLocation 663 overlayController.show(SENSOR_ID, REASON_UNKNOWN) 664 executor.runAllReady() 665 666 verify(windowManager, never()).addView(any(), any()) 667 } 668 669 fun alternateBouncerVisibility_showAndHideSideFpsUI() = testWithDisplay { 670 // WHEN alternate bouncer is visible 671 keyguardBouncerRepository.setAlternateVisible(true) 672 executor.runAllReady() 673 674 // THEN side fps shows UI 675 verify(windowManager).addView(any(), any()) 676 verify(windowManager, never()).removeView(any()) 677 678 // WHEN alternate bouncer is no longer visible 679 keyguardBouncerRepository.setAlternateVisible(false) 680 executor.runAllReady() 681 682 // THEN side fps UI is hidden 683 verify(windowManager).removeView(any()) 684 } 685 686 /** 687 * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0, 688 * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device 689 * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement 690 * in other rotations have been omitted. 691 */ 692 @Test 693 fun verifiesIndicatorPlacementForXAlignedSensor_0() = 694 testWithDisplay( 695 deviceConfig = DeviceConfig.X_ALIGNED, 696 isReverseDefaultRotation = false, 697 { rotation = Surface.ROTATION_0 } 698 ) { 699 sideFpsController.overlayOffsets = sensorLocation 700 701 sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds) 702 703 overlayController.show(SENSOR_ID, REASON_UNKNOWN) 704 executor.runAllReady() 705 706 verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture()) 707 assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX) 708 assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0) 709 } 710 711 /** 712 * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270 713 * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct 714 * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works 715 * correctly, tests for indicator placement in other rotations have been omitted. 716 */ 717 @Test 718 fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() = 719 testWithDisplay( 720 deviceConfig = DeviceConfig.X_ALIGNED, 721 isReverseDefaultRotation = true, 722 { rotation = Surface.ROTATION_270 } 723 ) { 724 sideFpsController.overlayOffsets = sensorLocation 725 726 sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds) 727 728 overlayController.show(SENSOR_ID, REASON_UNKNOWN) 729 executor.runAllReady() 730 731 verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture()) 732 assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX) 733 assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0) 734 } 735 736 /** 737 * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0, 738 * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device 739 * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement 740 * in other rotations have been omitted. 741 */ 742 @Test 743 fun verifiesIndicatorPlacementForYAlignedSensor_0() = 744 testWithDisplay( 745 deviceConfig = DeviceConfig.Y_ALIGNED, 746 isReverseDefaultRotation = false, 747 { rotation = Surface.ROTATION_0 } 748 ) { 749 sideFpsController.overlayOffsets = sensorLocation 750 751 sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds) 752 753 overlayController.show(SENSOR_ID, REASON_UNKNOWN) 754 executor.runAllReady() 755 756 verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture()) 757 assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth) 758 assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY) 759 } 760 761 /** 762 * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270 763 * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct 764 * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works 765 * correctly, tests for indicator placement in other rotations have been omitted. 766 */ 767 @Test 768 fun verifiesIndicatorPlacementForYAlignedSensor_InReverseDefaultRotation_270() = 769 testWithDisplay( 770 deviceConfig = DeviceConfig.Y_ALIGNED, 771 isReverseDefaultRotation = true, 772 { rotation = Surface.ROTATION_270 } 773 ) { 774 sideFpsController.overlayOffsets = sensorLocation 775 776 sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds) 777 778 overlayController.show(SENSOR_ID, REASON_UNKNOWN) 779 executor.runAllReady() 780 781 verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture()) 782 assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth) 783 assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY) 784 } 785 786 @Test 787 fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay { 788 // By default all those tests assume the side fps sensor is available. 789 assertThat(fingerprintManager.hasSideFpsSensor()).isTrue() 790 } 791 792 @Test 793 fun hasSideFpsSensor_withoutSensorProps_returnsFalse() { 794 whenEver(fingerprintManager.sensorPropertiesInternal).thenReturn(null) 795 796 assertThat(fingerprintManager.hasSideFpsSensor()).isFalse() 797 } 798 799 @Test 800 fun testLayoutParams_isKeyguardDialogType() = 801 testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) { 802 sideFpsController.overlayOffsets = sensorLocation 803 sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds) 804 overlayController.show(SENSOR_ID, REASON_UNKNOWN) 805 executor.runAllReady() 806 807 verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture()) 808 809 val lpType = overlayViewParamsCaptor.value.type 810 811 assertThat((lpType and TYPE_KEYGUARD_DIALOG) != 0).isTrue() 812 } 813 814 @Test 815 fun testLayoutParams_hasNoMoveAnimationWindowFlag() = 816 testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) { 817 sideFpsController.overlayOffsets = sensorLocation 818 sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds) 819 overlayController.show(SENSOR_ID, REASON_UNKNOWN) 820 executor.runAllReady() 821 822 verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture()) 823 824 val lpFlags = overlayViewParamsCaptor.value.privateFlags 825 826 assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue() 827 } 828 829 @Test 830 fun testLayoutParams_hasTrustedOverlayWindowFlag() = 831 testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) { 832 sideFpsController.overlayOffsets = sensorLocation 833 sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds) 834 overlayController.show(SENSOR_ID, REASON_UNKNOWN) 835 executor.runAllReady() 836 837 verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture()) 838 839 val lpFlags = overlayViewParamsCaptor.value.privateFlags 840 841 assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue() 842 } 843 844 @Test 845 fun primaryBouncerRequestAnimatesAlphaIn() = testWithDisplay { 846 sideFpsController.show(SideFpsUiRequestSource.PRIMARY_BOUNCER, REASON_AUTH_KEYGUARD) 847 executor.runAllReady() 848 verify(sideFpsView).animate() 849 } 850 851 @Test 852 fun alternateBouncerRequestsDoesNotAnimateAlphaIn() = testWithDisplay { 853 sideFpsController.show(SideFpsUiRequestSource.ALTERNATE_BOUNCER, REASON_AUTH_KEYGUARD) 854 executor.runAllReady() 855 verify(sideFpsView, never()).animate() 856 } 857 858 @Test 859 fun autoShowRequestsDoesNotAnimateAlphaIn() = testWithDisplay { 860 sideFpsController.show(SideFpsUiRequestSource.AUTO_SHOW, REASON_AUTH_KEYGUARD) 861 executor.runAllReady() 862 verify(sideFpsView, never()).animate() 863 } 864 } 865 866 private fun insetsForSmallNavbar() = insetsWithBottom(60) 867 868 private fun insetsForLargeNavbar() = insetsWithBottom(100) 869 870 private fun insetsWithBottom(bottom: Int) = 871 WindowInsets.Builder() 872 .setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, bottom)) 873 .build() 874 875 private fun fpEnrollTask() = settingsTask(".biometrics.fingerprint.FingerprintEnrollEnrolling") 876 877 private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings") 878 879 private fun settingsTask(cls: String) = 880 ActivityManager.RunningTaskInfo().apply { 881 topActivity = ComponentName.createRelative("com.android.settings", cls) 882 } 883