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

Commit d97c8972 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Fix BP face auth requiring full tap after auth" into udc-d1-dev

parents 004f1f8d 6ef648c9
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -644,8 +644,9 @@ public class UdfpsController implements DozeReceiver, Dumpable {
            shouldPilfer = true;
        }

        // Pilfer only once per gesture
        if (shouldPilfer && !mPointerPilfered) {
        // Pilfer only once per gesture, don't pilfer for BP
        if (shouldPilfer && !mPointerPilfered
                && getBiometricSessionType() != SESSION_BIOMETRIC_PROMPT) {
            mInputManager.pilferPointers(
                    mOverlay.getOverlayView().getViewRootImpl().getInputToken());
            mPointerPilfered = true;
+18 −9
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.ui.binder

import android.animation.Animator
import android.annotation.SuppressLint
import android.content.Context
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
@@ -25,6 +26,7 @@ import android.hardware.face.FaceManager
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
import android.view.accessibility.AccessibilityManager
@@ -68,6 +70,7 @@ private const val TAG = "BiometricViewBinder"
object BiometricViewBinder {

    /** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */
    @SuppressLint("ClickableViewAccessibility")
    @JvmStatic
    fun bind(
        view: BiometricPromptLayout,
@@ -293,21 +296,19 @@ object BiometricViewBinder {

                // reuse the icon as a confirm button
                launch {
                    viewModel.isConfirmButtonVisible
                    viewModel.isIconConfirmButton
                        .map { isPending ->
                            when {
                                isPending && iconController.actsAsConfirmButton ->
                                    View.OnClickListener { viewModel.confirmAuthenticated() }
                                else -> null
                                    View.OnTouchListener { _: View, event: MotionEvent ->
                                        viewModel.onOverlayTouch(event)
                                    }
                                else -> null
                            }
                        .collect { onClick ->
                            iconViewOverlay.setOnClickListener(onClick)
                            iconView.setOnClickListener(onClick)
                            if (onClick == null) {
                                iconViewOverlay.isClickable = false
                                iconView.isClickable = false
                        }
                        .collect { onTouch ->
                            iconViewOverlay.setOnTouchListener(onTouch)
                            iconView.setOnTouchListener(onTouch)
                        }
                }

@@ -333,6 +334,14 @@ object BiometricViewBinder {
                            backgroundView.setOnClickListener(null)
                            backgroundView.importantForAccessibility =
                                IMPORTANT_FOR_ACCESSIBILITY_NO

                            // Allow icon to be used as confirmation button with a11y enabled
                            if (accessibilityManager.isTouchExplorationEnabled) {
                                iconViewOverlay.setOnClickListener {
                                    viewModel.confirmAuthenticated()
                                }
                                iconView.setOnClickListener { viewModel.confirmAuthenticated() }
                            }
                        }
                        if (authState.isAuthenticatedAndConfirmed) {
                            view.announceForAccessibility(
+40 −12
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.systemui.biometrics.ui.viewmodel

import android.hardware.biometrics.BiometricPrompt
import android.util.Log
import android.view.MotionEvent
import com.android.systemui.biometrics.AuthBiometricView
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.model.BiometricModalities
@@ -63,11 +64,18 @@ constructor(
    /** If the user has successfully authenticated and confirmed (when explicitly required). */
    val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()

    private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)

    /**
     * If the API caller or the user's personal preferences require explicit confirmation after
     * successful authentication.
     */
    val isConfirmationRequired: Flow<Boolean> = interactor.isConfirmationRequired
    val isConfirmationRequired: Flow<Boolean> =
        combine(_isOverlayTouched, interactor.isConfirmationRequired) {
            isOverlayTouched,
            isConfirmationRequired ->
            !isOverlayTouched && isConfirmationRequired
        }

    /** The kind of credential the user has. */
    val credentialKind: Flow<PromptKind> = interactor.credentialKind
@@ -141,6 +149,12 @@ constructor(
            }
            .distinctUntilChanged()

    /** If the icon can be used as a confirmation button. */
    val isIconConfirmButton: Flow<Boolean> =
        combine(size, interactor.isConfirmationRequired) { size, isConfirmationRequired ->
            size.isNotSmall && isConfirmationRequired
        }

    /** If the negative button should be shown. */
    val isNegativeButtonVisible: Flow<Boolean> =
        combine(
@@ -286,8 +300,10 @@ constructor(
            if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
        _forceMediumSize.value = true
        _legacyState.value =
            if (alreadyAuthenticated) {
            if (alreadyAuthenticated && isConfirmationRequired.first()) {
                AuthBiometricView.STATE_PENDING_CONFIRMATION
            } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
                AuthBiometricView.STATE_AUTHENTICATED
            } else {
                AuthBiometricView.STATE_HELP
            }
@@ -385,18 +401,10 @@ constructor(
    }

    private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
        val availableModalities = modalities.first()
        val confirmationRequired = isConfirmationRequired.first()

        if (availableModalities.hasFaceAndFingerprint) {
            // coex only needs confirmation when face is successful, unless it happens on the
            // first attempt (i.e. without failure) before fingerprint scanning starts
            val fingerprintStarted = fingerprintStartMode.first() != FingerprintStartMode.Pending
        // Only worry about confirmationRequired if face was used to unlock
        if (modality == BiometricModality.Face) {
                return fingerprintStarted || confirmationRequired
            }
        }
        if (availableModalities.hasFaceOnly) {
            return confirmationRequired
        }
        // fingerprint only never requires confirmation
@@ -426,6 +434,26 @@ constructor(
        messageJob = null
    }

    /**
     * Touch event occurred on the overlay
     *
     * Tracks whether a finger is currently down to set [_isOverlayTouched] to be used as user
     * confirmation
     */
    fun onOverlayTouch(event: MotionEvent): Boolean {
        if (event.actionMasked == MotionEvent.ACTION_DOWN) {
            _isOverlayTouched.value = true

            if (_isAuthenticated.value.needsUserConfirmation) {
                confirmAuthenticated()
            }
            return true
        } else if (event.actionMasked == MotionEvent.ACTION_UP) {
            _isOverlayTouched.value = false
        }
        return false
    }

    /**
     * Switch to the credential view.
     *
+6 −1
Original line number Diff line number Diff line
@@ -499,6 +499,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
        val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
        val size by collectLastValue(viewModel.size)
        val legacyState by collectLastValue(viewModel.legacyState)
        val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired)

        if (testCase.isCoex && testCase.authenticatedByFingerprint) {
            viewModel.ensureFingerprintHasStarted(isDelayed = true)
@@ -507,7 +508,11 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
        viewModel.showHelp(helpMessage)

        assertThat(size).isEqualTo(PromptSize.MEDIUM)
        if (confirmationRequired == true) {
            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
        } else {
            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
        }
        assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
        assertThat(messageVisible).isTrue()
        assertThat(authenticating).isFalse()