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

Commit e4117b13 authored by Austin Delgado's avatar Austin Delgado
Browse files

Support BP retry face auth on SFPS acquired

Bug: 328638260
Test: atest PromptViewModelTest
Flag: None

Change-Id: I9566aa5ecab6d96054baba1eebcfa2748834f9d3
parent bb950542
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -457,6 +457,23 @@ object BiometricViewBinder {
                        }
                    }
                }

                // Retry and confirmation when finger on sensor
                launch {
                    combine(
                            viewModel.canTryAgainNow,
                            viewModel.hasFingerOnSensor,
                            viewModel.isPendingConfirmation,
                            ::Triple
                        )
                        .collect { (canRetry, fingerAcquired, pendingConfirmation) ->
                            if (canRetry && fingerAcquired) {
                                legacyCallback.onButtonTryAgain()
                            } else if (pendingConfirmation && fingerAcquired) {
                                viewModel.confirmAuthenticated()
                            }
                        }
                }
            }
        }

+22 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.content.pm.PackageManager
import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.Flags.customBiometricPrompt
import android.hardware.biometrics.PromptContentView
@@ -31,6 +32,7 @@ import android.view.MotionEvent
import com.android.systemui.Flags.bpTalkback
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
@@ -39,6 +41,7 @@ import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.Job
@@ -65,6 +68,7 @@ constructor(
    promptSelectorInteractor: PromptSelectorInteractor,
    @Application private val context: Context,
    private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
    private val biometricStatusInteractor: BiometricStatusInteractor,
    private val udfpsUtils: UdfpsUtils
) {
    /** The set of modalities available for this prompt */
@@ -147,6 +151,24 @@ constructor(
    /** Fingerprint sensor state. */
    val fingerprintStartMode: Flow<FingerprintStartMode> = _fingerprintStartMode.asStateFlow()

    /** Whether a finger has been acquired by the sensor */
    // TODO(b/331948073): Add support for detecting SFPS finger without authentication running
    val hasFingerBeenAcquired: Flow<Boolean> =
        combine(biometricStatusInteractor.fingerprintAcquiredStatus, modalities) {
                status,
                modalities ->
                modalities.hasSfps &&
                    status is AcquiredFingerprintAuthenticationStatus &&
                    status.acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
            }
            .distinctUntilChanged()

    /** Whether there is currently a finger on the sensor */
    val hasFingerOnSensor: Flow<Boolean> =
        combine(hasFingerBeenAcquired, _isOverlayTouched) { hasFingerBeenAcquired, overlayTouched ->
            hasFingerBeenAcquired || overlayTouched
        }

    private val _forceLargeSize = MutableStateFlow(false)
    private val _forceMediumSize = MutableStateFlow(false)

+10 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.systemui.biometrics

import android.app.ActivityTaskManager
import android.app.admin.DevicePolicyManager
import android.content.pm.PackageManager
import android.hardware.biometrics.BiometricAuthenticator
@@ -41,9 +42,12 @@ import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakePromptRepository
import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
@@ -116,10 +120,12 @@ open class AuthContainerViewTest : SysuiTestCase() {
    lateinit var selectedUserInteractor: SelectedUserInteractor
    @Mock
    private lateinit var packageManager: PackageManager
    @Mock private lateinit var activityTaskManager: ActivityTaskManager

    private val testScope = TestScope(StandardTestDispatcher())
    private val fakeExecutor = FakeExecutor(FakeSystemClock())
    private val biometricPromptRepository = FakePromptRepository()
    private val biometricStatusRepository = FakeBiometricStatusRepository()
    private val fingerprintRepository = FakeFingerprintPropertyRepository()
    private val displayStateRepository = FakeDisplayStateRepository()
    private val credentialInteractor = FakeCredentialInteractor()
@@ -139,6 +145,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
    private lateinit var displayRepository: FakeDisplayRepository
    private lateinit var displayStateInteractor: DisplayStateInteractor
    private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
    private lateinit var biometricStatusInteractor: BiometricStatusInteractor

    private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
    private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
@@ -164,6 +171,8 @@ open class AuthContainerViewTest : SysuiTestCase() {
                        selectedUserInteractor,
                        testScope.backgroundScope,
                )
        biometricStatusInteractor =
                BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
        // Set up default logo icon
        whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
        context.setMockPackageManager(packageManager)
@@ -577,6 +586,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
            promptSelectorInteractor,
            context,
            udfpsOverlayInteractor,
            biometricStatusInteractor,
            udfpsUtils
        ),
        { credentialViewModel },
+33 −11
Original line number Diff line number Diff line
@@ -16,12 +16,14 @@

package com.android.systemui.biometrics.ui.viewmodel

import android.app.ActivityTaskManager
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.drawable.BitmapDrawable
import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptContentItemBulletedText
import android.hardware.biometrics.PromptContentView
@@ -38,9 +40,12 @@ import com.android.systemui.Flags.FLAG_BP_TALKBACK
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakePromptRepository
import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
@@ -49,6 +54,7 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.extractAuthenticatorTypes
import com.android.systemui.biometrics.faceSensorPropertiesInternal
import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
import com.android.systemui.biometrics.shared.model.AuthenticationReason
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.DisplayRotation
@@ -57,6 +63,7 @@ import com.android.systemui.biometrics.shared.model.toSensorType
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
@@ -100,6 +107,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
    @Mock private lateinit var packageManager: PackageManager
    @Mock private lateinit var applicationInfoWithIcon: ApplicationInfo
    @Mock private lateinit var applicationInfoNoIcon: ApplicationInfo
    @Mock private lateinit var activityTaskManager: ActivityTaskManager

    private val fakeExecutor = FakeExecutor(FakeSystemClock())
    private val testScope = TestScope()
@@ -113,9 +121,11 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
    private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
    private lateinit var promptRepository: FakePromptRepository
    private lateinit var displayStateRepository: FakeDisplayStateRepository
    private lateinit var biometricStatusRepository: FakeBiometricStatusRepository
    private lateinit var displayRepository: FakeDisplayRepository
    private lateinit var displayStateInteractor: DisplayStateInteractor
    private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
    private lateinit var biometricStatusInteractor: BiometricStatusInteractor

    private lateinit var selector: PromptSelectorInteractor
    private lateinit var viewModel: PromptViewModel
@@ -153,6 +163,9 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
                selectedUserInteractor,
                testScope.backgroundScope
            )
        biometricStatusRepository = FakeBiometricStatusRepository()
        biometricStatusInteractor =
            BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
        selector =
            PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
        selector.resetPrompt()
@@ -168,6 +181,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
                selector,
                mContext,
                udfpsOverlayInteractor,
                biometricStatusInteractor,
                udfpsUtils
            )
        iconViewModel = viewModel.iconViewModel
@@ -1043,8 +1057,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
    fun auto_confirm_authentication_when_finger_down() = runGenericTest {
        val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)

        // No icon button when face only, can't confirm before auth
        if (!testCase.isFaceOnly) {
        if (testCase.isCoex) {
            viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
        }
        viewModel.showAuthenticated(testCase.authenticatedModality, 0)
@@ -1059,7 +1072,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
        assertThat(canTryAgain).isFalse()
        assertThat(authenticated?.isAuthenticated).isTrue()

        if (testCase.isFaceOnly && expectConfirmation) {
        if (expectConfirmation) {
            if (testCase.isFaceOnly) {
                assertThat(size).isEqualTo(PromptSize.MEDIUM)
                assertButtonsVisible(
                    cancel = true,
@@ -1067,6 +1081,9 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
                )

                viewModel.confirmAuthenticated()
            } else if (testCase.isCoex) {
                assertThat(authenticated?.isAuthenticatedAndConfirmed).isTrue()
            }
            assertThat(message).isEqualTo(PromptMessage.Empty)
            assertButtonsVisible()
        }
@@ -1076,8 +1093,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
    fun cannot_auto_confirm_authentication_when_finger_up() = runGenericTest {
        val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)

        // No icon button when face only, can't confirm before auth
        if (!testCase.isFaceOnly) {
        if (testCase.isCoex) {
            viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
            viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_UP))
        }
@@ -1379,6 +1395,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
            packageName = packageName,
        )

        biometricStatusRepository.setFingerprintAcquiredStatus(
            AcquiredFingerprintAuthenticationStatus(
                AuthenticationReason.BiometricPromptAuthentication,
                BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_UNKNOWN
            )
        )
        // put the view model in the initial authenticating state, unless explicitly skipped
        val startMode =
            when {
+2 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.ui.viewmodel

import android.content.applicationContext
import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
import com.android.systemui.biometrics.domain.interactor.promptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
@@ -30,6 +31,7 @@ val Kosmos.promptViewModel by Fixture {
        promptSelectorInteractor = promptSelectorInteractor,
        context = applicationContext,
        udfpsOverlayInteractor = udfpsOverlayInteractor,
        biometricStatusInteractor = biometricStatusInteractor,
        udfpsUtils = udfpsUtils
    )
}