Merge "Fix BP face auth requiring full tap after auth" into udc-d1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
d97c897215
@@ -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;
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user