Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 521960d1 authored by Grace Cheng's avatar Grace Cheng Committed by Android (Google) Code Review
Browse files

Merge "BiometricPrompt test cleanup" into main

parents 5c9e4399 42a5bd4c
Loading
Loading
Loading
Loading
+84 −9
Original line number Diff line number Diff line
@@ -27,12 +27,23 @@ import android.hardware.face.FaceSensorProperties
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.SysuiTestableContext
import com.android.systemui.biometrics.data.repository.biometricStatusRepository
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.res.R
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent

/** Create [FingerprintSensorPropertiesInternal] for a test. */
internal fun fingerprintSensorPropertiesInternal(
    ids: List<Int> = listOf(0),
    strong: Boolean = true,
    sensorType: Int = FingerprintSensorProperties.TYPE_REAR
    sensorType: Int = FingerprintSensorProperties.TYPE_REAR,
): List<FingerprintSensorPropertiesInternal> {
    val componentInfo =
        listOf(
@@ -41,15 +52,15 @@ internal fun fingerprintSensorPropertiesInternal(
                "vendor/model/revision" /* hardwareVersion */,
                "1.01" /* firmwareVersion */,
                "00000001" /* serialNumber */,
                "" /* softwareVersion */
                "", /* softwareVersion */
            ),
            ComponentInfoInternal(
                "matchingAlgorithm" /* componentId */,
                "" /* hardwareVersion */,
                "" /* firmwareVersion */,
                "" /* serialNumber */,
                "vendor/version/revision" /* softwareVersion */
            )
                "vendor/version/revision", /* softwareVersion */
            ),
        )
    return ids.map { id ->
        FingerprintSensorPropertiesInternal(
@@ -58,7 +69,7 @@ internal fun fingerprintSensorPropertiesInternal(
            5 /* maxEnrollmentsPerUser */,
            componentInfo,
            sensorType,
            false /* resetLockoutRequiresHardwareAuthToken */
            false, /* resetLockoutRequiresHardwareAuthToken */
        )
    }
}
@@ -75,15 +86,15 @@ internal fun faceSensorPropertiesInternal(
                "vendor/model/revision" /* hardwareVersion */,
                "1.01" /* firmwareVersion */,
                "00000001" /* serialNumber */,
                "" /* softwareVersion */
                "", /* softwareVersion */
            ),
            ComponentInfoInternal(
                "matchingAlgorithm" /* componentId */,
                "" /* hardwareVersion */,
                "" /* firmwareVersion */,
                "" /* serialNumber */,
                "vendor/version/revision" /* softwareVersion */
            )
                "vendor/version/revision", /* softwareVersion */
            ),
        )
    return ids.map { id ->
        FaceSensorPropertiesInternal(
@@ -94,7 +105,7 @@ internal fun faceSensorPropertiesInternal(
            FaceSensorProperties.TYPE_RGB,
            true /* supportsFaceDetection */,
            true /* supportsSelfIllumination */,
            false /* resetLockoutRequiresHardwareAuthToken */
            false, /* resetLockoutRequiresHardwareAuthToken */
        )
    }
}
@@ -145,3 +156,67 @@ internal fun promptInfo(
    info.negativeButtonText = negativeButton
    return info
}

@OptIn(ExperimentalCoroutinesApi::class)
internal fun TestScope.updateSfpsIndicatorRequests(
    kosmos: Kosmos,
    mContext: SysuiTestableContext,
    primaryBouncerRequest: Boolean? = null,
    alternateBouncerRequest: Boolean? = null,
    biometricPromptRequest: Boolean? = null,
    // TODO(b/365182034): update when rest to unlock feature is implemented
    //    progressBarShowing: Boolean? = null
) {
    biometricPromptRequest?.let { hasBiometricPromptRequest ->
        if (hasBiometricPromptRequest) {
            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
                AuthenticationReason.BiometricPromptAuthentication
            )
        } else {
            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
                AuthenticationReason.NotRunning
            )
        }
    }

    primaryBouncerRequest?.let { hasPrimaryBouncerRequest ->
        updatePrimaryBouncer(
            kosmos,
            mContext,
            isShowing = hasPrimaryBouncerRequest,
            isAnimatingAway = false,
            fpsDetectionRunning = true,
            isUnlockingWithFpAllowed = true,
        )
    }

    alternateBouncerRequest?.let { hasAlternateBouncerRequest ->
        kosmos.keyguardBouncerRepository.setAlternateVisible(hasAlternateBouncerRequest)
    }

    // TODO(b/365182034): set progress bar visibility when rest to unlock feature is implemented

    runCurrent()
}

internal fun updatePrimaryBouncer(
    kosmos: Kosmos,
    mContext: SysuiTestableContext,
    isShowing: Boolean,
    isAnimatingAway: Boolean,
    fpsDetectionRunning: Boolean,
    isUnlockingWithFpAllowed: Boolean,
) {
    kosmos.keyguardBouncerRepository.setPrimaryShow(isShowing)
    kosmos.keyguardBouncerRepository.setPrimaryStartingToHide(false)
    val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null
    kosmos.keyguardBouncerRepository.setPrimaryStartDisappearAnimation(
        primaryStartDisappearAnimation
    )

    whenever(kosmos.keyguardUpdateMonitor.isFingerprintDetectionRunning)
        .thenReturn(fpsDetectionRunning)
    whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
        .thenReturn(isUnlockingWithFpAllowed)
    mContext.orCreateTestableResources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, true)
}
+34 −223
Original line number Diff line number Diff line
@@ -16,64 +16,48 @@

package com.android.systemui.biometrics.ui.binder

import android.animation.Animator
import android.graphics.Rect
import android.hardware.biometrics.SensorLocationInternal
import android.hardware.display.DisplayManager
import android.hardware.display.DisplayManagerGlobal
import android.testing.TestableLooper
import android.view.Display
import android.view.DisplayInfo
import android.view.LayoutInflater
import android.view.View
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.WindowManager
import android.view.WindowMetrics
import android.view.layoutInflater
import android.view.windowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider
import com.android.systemui.biometrics.data.repository.biometricStatusRepository
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
import com.android.systemui.biometrics.updateSfpsIndicatorRequests
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.displayStateRepository
import com.android.systemui.keyguard.ui.viewmodel.sideFpsProgressBarViewModel
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.firstValue

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -83,84 +67,25 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
    private val kosmos = testKosmos()

    @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
    @Mock private lateinit var displayManager: DisplayManager
    @Mock
    private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider
    @Mock private lateinit var layoutInflater: LayoutInflater
    @Mock private lateinit var sideFpsView: View

    private val contextDisplayInfo = DisplayInfo()

    private var displayWidth: Int = 0
    private var displayHeight: Int = 0
    private var boundsWidth: Int = 0
    private var boundsHeight: Int = 0

    private lateinit var deviceConfig: DeviceConfig
    private lateinit var sensorLocation: SensorLocationInternal

    enum class DeviceConfig {
        X_ALIGNED,
        Y_ALIGNED,
    }
    @Captor private lateinit var viewCaptor: ArgumentCaptor<View>

    @Before
    fun setup() {
        allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread

        mContext = spy(mContext)

        val resources = mContext.resources
        whenever(mContext.display)
            .thenReturn(
                Display(mock(DisplayManagerGlobal::class.java), 1, contextDisplayInfo, resources)
            )

        kosmos.layoutInflater = layoutInflater

        whenever(fingerprintInteractiveToAuthProvider.enabledForCurrentUser)
            .thenReturn(MutableStateFlow(false))

        context.addMockSystemService(DisplayManager::class.java, displayManager)
        context.addMockSystemService(WindowManager::class.java, kosmos.windowManager)

        `when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
        `when`(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
            .thenReturn(mock(LottieAnimationView::class.java))
        with(mock(ViewPropertyAnimator::class.java)) {
            `when`(sideFpsView.animate()).thenReturn(this)
            `when`(alpha(Mockito.anyFloat())).thenReturn(this)
            `when`(setStartDelay(Mockito.anyLong())).thenReturn(this)
            `when`(setDuration(Mockito.anyLong())).thenReturn(this)
            `when`(setListener(any())).thenAnswer {
                (it.arguments[0] as Animator.AnimatorListener).onAnimationEnd(
                    mock(Animator::class.java)
                )
                this
            }
        }
    }

    @Test
    fun verifyIndicatorNotAdded_whenInRearDisplayMode() {
        kosmos.testScope.runTest {
            setupTestConfiguration(
                DeviceConfig.X_ALIGNED,
                rotation = DisplayRotation.ROTATION_0,
                isInRearDisplayMode = true
            )
            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
                AuthenticationReason.NotRunning
            )
            kosmos.sideFpsProgressBarViewModel.setVisible(false)
            updatePrimaryBouncer(
                isShowing = true,
                isAnimatingAway = false,
                fpsDetectionRunning = true,
                isUnlockingWithFpAllowed = true
            )
            runCurrent()

            setupTestConfiguration(isInRearDisplayMode = true)
            updateSfpsIndicatorRequests(kosmos, mContext, primaryBouncerRequest = true)
            verify(kosmos.windowManager, never()).addView(any(), any())
        }
    }
@@ -168,33 +93,14 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
    @Test
    fun verifyIndicatorShowAndHide_onPrimaryBouncerShowAndHide() {
        kosmos.testScope.runTest {
            setupTestConfiguration(
                DeviceConfig.X_ALIGNED,
                rotation = DisplayRotation.ROTATION_0,
                isInRearDisplayMode = false
            )
            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
                AuthenticationReason.NotRunning
            )
            kosmos.sideFpsProgressBarViewModel.setVisible(false)
            // Show primary bouncer
            updatePrimaryBouncer(
                isShowing = true,
                isAnimatingAway = false,
                fpsDetectionRunning = true,
                isUnlockingWithFpAllowed = true
            )
            setupTestConfiguration(isInRearDisplayMode = false)
            updateSfpsIndicatorRequests(kosmos, mContext, primaryBouncerRequest = true)
            runCurrent()

            verify(kosmos.windowManager).addView(any(), any())

            // Hide primary bouncer
            updatePrimaryBouncer(
                isShowing = false,
                isAnimatingAway = false,
                fpsDetectionRunning = true,
                isUnlockingWithFpAllowed = true
            )
            updateSfpsIndicatorRequests(kosmos, mContext, primaryBouncerRequest = false)
            runCurrent()

            verify(kosmos.windowManager).removeView(any())
@@ -204,30 +110,19 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
    @Test
    fun verifyIndicatorShowAndHide_onAlternateBouncerShowAndHide() {
        kosmos.testScope.runTest {
            setupTestConfiguration(
                DeviceConfig.X_ALIGNED,
                rotation = DisplayRotation.ROTATION_0,
                isInRearDisplayMode = false
            )
            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
                AuthenticationReason.NotRunning
            )
            kosmos.sideFpsProgressBarViewModel.setVisible(false)
            // Show alternate bouncer
            kosmos.keyguardBouncerRepository.setAlternateVisible(true)
            setupTestConfiguration(isInRearDisplayMode = false)
            updateSfpsIndicatorRequests(kosmos, mContext, alternateBouncerRequest = true)
            runCurrent()

            verify(kosmos.windowManager).addView(any(), any())

            var viewCaptor = argumentCaptor<View>()
            verify(kosmos.windowManager).addView(viewCaptor.capture(), any())
            verify(viewCaptor.firstValue)
                .announceForAccessibility(
                    mContext.getText(R.string.accessibility_side_fingerprint_indicator_label)
                )

            // Hide alternate bouncer
            kosmos.keyguardBouncerRepository.setAlternateVisible(false)
            updateSfpsIndicatorRequests(kosmos, mContext, alternateBouncerRequest = false)
            runCurrent()

            verify(kosmos.windowManager).removeView(any())
@@ -237,30 +132,14 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
    @Test
    fun verifyIndicatorShownAndHidden_onSystemServerAuthenticationStartedAndStopped() {
        kosmos.testScope.runTest {
            setupTestConfiguration(
                DeviceConfig.X_ALIGNED,
                rotation = DisplayRotation.ROTATION_0,
                isInRearDisplayMode = false
            )
            kosmos.sideFpsProgressBarViewModel.setVisible(false)
            updatePrimaryBouncer(
                isShowing = false,
                isAnimatingAway = false,
                fpsDetectionRunning = true,
                isUnlockingWithFpAllowed = true
            )
            // System server authentication started
            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
                AuthenticationReason.BiometricPromptAuthentication
            )
            setupTestConfiguration(isInRearDisplayMode = false)
            updateSfpsIndicatorRequests(kosmos, mContext, biometricPromptRequest = true)
            runCurrent()

            verify(kosmos.windowManager).addView(any(), any())

            // System server authentication stopped
            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
                AuthenticationReason.NotRunning
            )
            updateSfpsIndicatorRequests(kosmos, mContext, biometricPromptRequest = false)
            runCurrent()

            verify(kosmos.windowManager).removeView(any())
@@ -269,45 +148,37 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {

    // On progress bar shown - hide indicator
    // On progress bar hidden - show indicator
    // TODO(b/365182034): update + enable when rest to unlock feature is implemented
    @Ignore("b/365182034")
    @Test
    fun verifyIndicatorProgressBarInteraction() {
        kosmos.testScope.runTest {
            // Pre-auth conditions
            setupTestConfiguration(
                DeviceConfig.X_ALIGNED,
                rotation = DisplayRotation.ROTATION_0,
                isInRearDisplayMode = false
            )
            kosmos.biometricStatusRepository.setFingerprintAuthenticationReason(
                AuthenticationReason.NotRunning
            )
            kosmos.sideFpsProgressBarViewModel.setVisible(false)

            // Show primary bouncer
            updatePrimaryBouncer(
                isShowing = true,
                isAnimatingAway = false,
                fpsDetectionRunning = true,
                isUnlockingWithFpAllowed = true
            )
            setupTestConfiguration(isInRearDisplayMode = false)
            updateSfpsIndicatorRequests(kosmos, mContext, primaryBouncerRequest = true)
            runCurrent()

            val inOrder = inOrder(kosmos.windowManager)

            // Verify indicator shown
            inOrder.verify(kosmos.windowManager).addView(any(), any())

            // Set progress bar visible
            kosmos.sideFpsProgressBarViewModel.setVisible(true)

            updateSfpsIndicatorRequests(
                kosmos,
                mContext,
                primaryBouncerRequest = true,
            ) // , progressBarShowing = true)
            runCurrent()

            // Verify indicator hidden
            inOrder.verify(kosmos.windowManager).removeView(any())

            // Set progress bar invisible
            kosmos.sideFpsProgressBarViewModel.setVisible(false)

            updateSfpsIndicatorRequests(
                kosmos,
                mContext,
                primaryBouncerRequest = true,
            ) // , progressBarShowing = false)
            runCurrent()

            // Verify indicator shown
@@ -315,78 +186,18 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
        }
    }

    private fun updatePrimaryBouncer(
        isShowing: Boolean,
        isAnimatingAway: Boolean,
        fpsDetectionRunning: Boolean,
        isUnlockingWithFpAllowed: Boolean,
    ) {
        kosmos.keyguardBouncerRepository.setPrimaryShow(isShowing)
        kosmos.keyguardBouncerRepository.setPrimaryStartingToHide(false)
        val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null
        kosmos.keyguardBouncerRepository.setPrimaryStartDisappearAnimation(
            primaryStartDisappearAnimation
        )

        whenever(kosmos.keyguardUpdateMonitor.isFingerprintDetectionRunning)
            .thenReturn(fpsDetectionRunning)
        whenever(kosmos.keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
            .thenReturn(isUnlockingWithFpAllowed)
        mContext.orCreateTestableResources.addOverride(
            R.bool.config_show_sidefps_hint_on_bouncer,
            true
        )
    }

    private suspend fun TestScope.setupTestConfiguration(
        deviceConfig: DeviceConfig,
        rotation: DisplayRotation = DisplayRotation.ROTATION_0,
        isInRearDisplayMode: Boolean,
    ) {
        this@SideFpsOverlayViewBinderTest.deviceConfig = deviceConfig

        when (deviceConfig) {
            DeviceConfig.X_ALIGNED -> {
                displayWidth = 3000
                displayHeight = 1500
                boundsWidth = 200
                boundsHeight = 100
                sensorLocation = SensorLocationInternal("", 2500, 0, boundsWidth / 2)
            }
            DeviceConfig.Y_ALIGNED -> {
                displayWidth = 2500
                displayHeight = 2000
                boundsWidth = 100
                boundsHeight = 200
                sensorLocation = SensorLocationInternal("", displayWidth, 300, boundsHeight / 2)
            }
        }

        whenever(kosmos.windowManager.maximumWindowMetrics)
            .thenReturn(
                WindowMetrics(
                    Rect(0, 0, displayWidth, displayHeight),
                    mock(WindowInsets::class.java),
                )
            )

        contextDisplayInfo.uniqueId = DISPLAY_ID

    private suspend fun TestScope.setupTestConfiguration(isInRearDisplayMode: Boolean) {
        kosmos.fingerprintPropertyRepository.setProperties(
            sensorId = 1,
            strength = SensorStrength.STRONG,
            sensorType = FingerprintSensorType.POWER_BUTTON,
            sensorLocations = mapOf(DISPLAY_ID to sensorLocation)
            sensorLocations = emptyMap(),
        )

        kosmos.displayStateRepository.setIsInRearDisplayMode(isInRearDisplayMode)
        kosmos.displayStateRepository.setCurrentRotation(rotation)
        kosmos.displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
        kosmos.displayRepository.emitDisplayChangeEvent(0)
        kosmos.sideFpsOverlayViewBinder.start()
        runCurrent()
    }

    companion object {
        private const val DISPLAY_ID = "displayId"
    }
}
+12 −69

File changed.

Preview size limit exceeded, changes collapsed.