diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-08-13 15:07:46 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-08-13 15:07:46 +0000 |
commit | b7a4d2af596d76d556e856d7d983e4a4adeb7d56 (patch) | |
tree | ee27d837c885bd8776eef58d6b5b19b316cadae5 | |
parent | 45a26ffd7c66e8805fb59b01b3f3a369cbc1321d (diff) | |
parent | 8762a4607896ff235493fbd9529c9989fdafbad6 (diff) | |
download | base-b7a4d2af596d76d556e856d7d983e4a4adeb7d56.tar.gz |
Merge cherrypicks of ['googleplex-android-review.googlesource.com/24295196', 'googleplex-android-review.googlesource.com/24405605'] into udc-d1-release.
Change-Id: I96b6b54656d038ff49966e0b32577e585a8164a7
8 files changed, 75 insertions, 29 deletions
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 79426c8311ab..dfff8d4d28ff 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1835,6 +1835,8 @@ <string name="fingerprint_error_not_match">Fingerprint not recognized</string> <!-- Message shown when UDFPS fails to match --> <string name="fingerprint_udfps_error_not_match">Fingerprint not recognized</string> + <!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] --> + <string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string> <!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] --> <string name="fingerprint_authenticated">Fingerprint authenticated</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index cafa74e7250b..4213190a4ac2 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2591,6 +2591,7 @@ <java-symbol type="string" name="fingerprint_error_vendor_unknown" /> <java-symbol type="string" name="fingerprint_error_not_match" /> <java-symbol type="string" name="fingerprint_udfps_error_not_match" /> + <java-symbol type="string" name="fingerprint_dialog_use_fingerprint_instead" /> <java-symbol type="string" name="fingerprint_acquired_partial" /> <java-symbol type="string" name="fingerprint_acquired_insufficient" /> <java-symbol type="string" name="fingerprint_acquired_imager_dirty" /> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 96b144731a1b..2ca4f0cfe3b1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -88,6 +88,8 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -101,7 +103,6 @@ import java.util.Set; import javax.inject.Inject; import javax.inject.Provider; -import kotlin.Unit; import kotlinx.coroutines.CoroutineScope; /** @@ -1036,7 +1037,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, final int userId = mCurrentDialogArgs.argi1; if (isFaceAuthEnrolled(userId) && isFingerprintEnrolled(userId)) { messageRes = modality == TYPE_FACE - ? R.string.biometric_face_not_recognized + ? R.string.fingerprint_dialog_use_fingerprint_instead : R.string.fingerprint_error_not_match; } else { messageRes = R.string.biometric_not_recognized; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 16d12bbfe3b4..0ccda1f5fd7c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index f7625cd9b859..1bf7ffa8cff6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -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() } + 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( @@ -476,7 +485,7 @@ private class Spaghetti( modalities.hasFaceAndFingerprint && (viewModel.fingerprintStartMode.first() != FingerprintStartMode.Pending) && (authenticatedModality == BiometricModality.Face) -> - R.string.biometric_dialog_tap_confirm_with_face + R.string.biometric_dialog_tap_confirm_with_face_alt_1 else -> null } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 8a2e4059ee73..a148d087eb3b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -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 - 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 @@ -427,6 +435,26 @@ constructor( } /** + * 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. * * TODO(b/251476085): this should be decoupled from the shared panel controller diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index 0a73a9eba9ee..8278135b2f9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -99,7 +99,6 @@ import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; import com.android.systemui.util.concurrency.FakeExecution; @@ -515,7 +514,7 @@ public class AuthControllerTest extends SysuiTestCase { assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality); assertThat(mMessageCaptor.getValue()).isEqualTo( - mContext.getString(R.string.biometric_face_not_recognized)); + mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 91140a9b0fc4..eed7b666fc71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -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) - 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() |