Merge "[conflict] Merge "Fix BP face auth requiring full tap after auth" into udc-d1-dev am: d97c897215" into udc-qpr-dev

This commit is contained in:
Greg Kaiser
2023-08-14 21:06:26 +00:00
committed by Android (Google) Code Review
4 changed files with 67 additions and 24 deletions

View File

@@ -646,8 +646,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;

View File

@@ -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
@@ -26,6 +27,7 @@ import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.View
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
import android.view.accessibility.AccessibilityManager
@@ -73,6 +75,7 @@ private const val TAG = "BiometricViewBinder"
object BiometricViewBinder {
/** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */
@SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
view: BiometricPromptLayout,
@@ -300,21 +303,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() }
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)
}
}
@@ -340,6 +341,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(

View File

@@ -18,6 +18,7 @@ package com.android.systemui.biometrics.ui.viewmodel
import android.hardware.biometrics.BiometricPrompt
import android.util.Log
import android.view.HapticFeedbackConstants
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
@@ -67,11 +68,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
@@ -150,6 +158,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(
@@ -298,8 +312,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
}
@@ -397,18 +413,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
if (modality == BiometricModality.Face) {
return fingerprintStarted || confirmationRequired
}
}
if (availableModalities.hasFaceOnly) {
// Only worry about confirmationRequired if face was used to unlock
if (modality == BiometricModality.Face) {
return confirmationRequired
}
// fingerprint only never requires confirmation
@@ -438,6 +446,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.
*

View File

@@ -527,6 +527,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)
@@ -535,7 +536,11 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
viewModel.showHelp(helpMessage)
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(messageVisible).isTrue()
assertThat(authenticating).isFalse()