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

This commit is contained in:
Treehugger Robot
2023-08-12 01:35:58 +00:00
committed by Android (Google) Code Review
4 changed files with 67 additions and 24 deletions

View File

@@ -644,8 +644,9 @@ public class UdfpsController implements DozeReceiver, Dumpable {
shouldPilfer = true; shouldPilfer = true;
} }
// Pilfer only once per gesture // Pilfer only once per gesture, don't pilfer for BP
if (shouldPilfer && !mPointerPilfered) { if (shouldPilfer && !mPointerPilfered
&& getBiometricSessionType() != SESSION_BIOMETRIC_PROMPT) {
mInputManager.pilferPointers( mInputManager.pilferPointers(
mOverlay.getOverlayView().getViewRootImpl().getInputToken()); mOverlay.getOverlayView().getViewRootImpl().getInputToken());
mPointerPilfered = true; mPointerPilfered = true;

View File

@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.ui.binder package com.android.systemui.biometrics.ui.binder
import android.animation.Animator import android.animation.Animator
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricConstants
@@ -25,6 +26,7 @@ import android.hardware.face.FaceManager
import android.os.Bundle import android.os.Bundle
import android.text.method.ScrollingMovementMethod import android.text.method.ScrollingMovementMethod
import android.util.Log import android.util.Log
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager
@@ -68,6 +70,7 @@ private const val TAG = "BiometricViewBinder"
object BiometricViewBinder { object BiometricViewBinder {
/** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */ /** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */
@SuppressLint("ClickableViewAccessibility")
@JvmStatic @JvmStatic
fun bind( fun bind(
view: BiometricPromptLayout, view: BiometricPromptLayout,
@@ -293,21 +296,19 @@ object BiometricViewBinder {
// reuse the icon as a confirm button // reuse the icon as a confirm button
launch { launch {
viewModel.isConfirmButtonVisible viewModel.isIconConfirmButton
.map { isPending -> .map { isPending ->
when { when {
isPending && iconController.actsAsConfirmButton -> isPending && iconController.actsAsConfirmButton ->
View.OnClickListener { viewModel.confirmAuthenticated() } View.OnTouchListener { _: View, event: MotionEvent ->
viewModel.onOverlayTouch(event)
}
else -> null else -> null
} }
} }
.collect { onClick -> .collect { onTouch ->
iconViewOverlay.setOnClickListener(onClick) iconViewOverlay.setOnTouchListener(onTouch)
iconView.setOnClickListener(onClick) iconView.setOnTouchListener(onTouch)
if (onClick == null) {
iconViewOverlay.isClickable = false
iconView.isClickable = false
}
} }
} }
@@ -333,6 +334,14 @@ object BiometricViewBinder {
backgroundView.setOnClickListener(null) backgroundView.setOnClickListener(null)
backgroundView.importantForAccessibility = backgroundView.importantForAccessibility =
IMPORTANT_FOR_ACCESSIBILITY_NO 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) { if (authState.isAuthenticatedAndConfirmed) {
view.announceForAccessibility( view.announceForAccessibility(

View File

@@ -17,6 +17,7 @@ package com.android.systemui.biometrics.ui.viewmodel
import android.hardware.biometrics.BiometricPrompt import android.hardware.biometrics.BiometricPrompt
import android.util.Log import android.util.Log
import android.view.MotionEvent
import com.android.systemui.biometrics.AuthBiometricView import com.android.systemui.biometrics.AuthBiometricView
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.model.BiometricModalities import com.android.systemui.biometrics.domain.model.BiometricModalities
@@ -63,11 +64,18 @@ constructor(
/** If the user has successfully authenticated and confirmed (when explicitly required). */ /** If the user has successfully authenticated and confirmed (when explicitly required). */
val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow() 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 * If the API caller or the user's personal preferences require explicit confirmation after
* successful authentication. * 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. */ /** The kind of credential the user has. */
val credentialKind: Flow<PromptKind> = interactor.credentialKind val credentialKind: Flow<PromptKind> = interactor.credentialKind
@@ -141,6 +149,12 @@ constructor(
} }
.distinctUntilChanged() .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. */ /** If the negative button should be shown. */
val isNegativeButtonVisible: Flow<Boolean> = val isNegativeButtonVisible: Flow<Boolean> =
combine( combine(
@@ -286,8 +300,10 @@ constructor(
if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
_forceMediumSize.value = true _forceMediumSize.value = true
_legacyState.value = _legacyState.value =
if (alreadyAuthenticated) { if (alreadyAuthenticated && isConfirmationRequired.first()) {
AuthBiometricView.STATE_PENDING_CONFIRMATION AuthBiometricView.STATE_PENDING_CONFIRMATION
} else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
AuthBiometricView.STATE_AUTHENTICATED
} else { } else {
AuthBiometricView.STATE_HELP AuthBiometricView.STATE_HELP
} }
@@ -385,18 +401,10 @@ constructor(
} }
private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean { private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
val availableModalities = modalities.first()
val confirmationRequired = isConfirmationRequired.first() val confirmationRequired = isConfirmationRequired.first()
if (availableModalities.hasFaceAndFingerprint) { // Only worry about confirmationRequired if face was used to unlock
// coex only needs confirmation when face is successful, unless it happens on the if (modality == BiometricModality.Face) {
// first attempt (i.e. without failure) before fingerprint scanning starts
val fingerprintStarted = fingerprintStartMode.first() != FingerprintStartMode.Pending
if (modality == BiometricModality.Face) {
return fingerprintStarted || confirmationRequired
}
}
if (availableModalities.hasFaceOnly) {
return confirmationRequired return confirmationRequired
} }
// fingerprint only never requires confirmation // fingerprint only never requires confirmation
@@ -426,6 +434,26 @@ constructor(
messageJob = null 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. * Switch to the credential view.
* *

View File

@@ -499,6 +499,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible) val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
val size by collectLastValue(viewModel.size) val size by collectLastValue(viewModel.size)
val legacyState by collectLastValue(viewModel.legacyState) val legacyState by collectLastValue(viewModel.legacyState)
val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired)
if (testCase.isCoex && testCase.authenticatedByFingerprint) { if (testCase.isCoex && testCase.authenticatedByFingerprint) {
viewModel.ensureFingerprintHasStarted(isDelayed = true) viewModel.ensureFingerprintHasStarted(isDelayed = true)
@@ -507,7 +508,11 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
viewModel.showHelp(helpMessage) viewModel.showHelp(helpMessage)
assertThat(size).isEqualTo(PromptSize.MEDIUM) assertThat(size).isEqualTo(PromptSize.MEDIUM)
assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION) if (confirmationRequired == true) {
assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
} else {
assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
}
assertThat(message).isEqualTo(PromptMessage.Help(helpMessage)) assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
assertThat(messageVisible).isTrue() assertThat(messageVisible).isTrue()
assertThat(authenticating).isFalse() assertThat(authenticating).isFalse()