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

Commit a1a641a6 authored by Austin Delgado's avatar Austin Delgado Committed by Android (Google) Code Review
Browse files

Merge changes If89f9429,If1896097,I25290c02 into main

* changes:
  Update content description for Biometric Prompt UDFPS icon
  Remove Biometric Prompt authentication a11y announcement
  Fix Biometric Prompt icon acting as button for a11y
parents 90da804a d01f40ea
Loading
Loading
Loading
Loading
+0 −10
Original line number Diff line number Diff line
@@ -3,16 +3,6 @@ container: "system"

# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.

flag {
  name: "bp_icon_a11y"
  namespace: "biometrics_framework"
  description: "Fixes biometric prompt icon not working as button with a11y"
  bug: "359423579"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}

flag {
    name: "cont_auth_plugin"
    namespace: "biometrics_framework"
+39 −41
Original line number Diff line number Diff line
@@ -158,6 +158,22 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
    private val mockFingerprintIconWidth = 300
    private val mockFingerprintIconHeight = 300

    private val faceIconAuthingDescription =
        R.string.biometric_dialog_face_icon_description_authenticating
    private val faceIconAuthedDescription =
        R.string.biometric_dialog_face_icon_description_authenticated
    private val faceIconConfirmedDescription =
        R.string.biometric_dialog_face_icon_description_confirmed
    private val faceIconIdleDescription = R.string.biometric_dialog_face_icon_description_idle
    private val sfpsFindSensorDescription =
        R.string.security_settings_sfps_enroll_find_sensor_message
    private val udfpsIconDescription = R.string.accessibility_fingerprint_label
    private val faceFailedDescription = R.string.keyguard_face_failed
    private val bpTryAgainDescription = R.string.biometric_dialog_try_again
    private val bpConfirmDescription = R.string.biometric_dialog_confirm
    private val fingerprintIconAuthenticatedDescription =
        R.string.fingerprint_dialog_authenticated_confirmation

    /** Mock [UdfpsOverlayParams] for a test. */
    private fun mockUdfpsOverlayParams(isLandscape: Boolean = false): UdfpsOverlayParams =
        UdfpsOverlayParams(
@@ -337,21 +353,18 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
            if ((testCase.isCoex && !forceExplicitFlow) || testCase.isFaceOnly) {
                // Face-only or implicit co-ex auth
                assertThat(iconAsset).isEqualTo(R.raw.face_dialog_authenticating)
                assertThat(iconContentDescriptionId)
                    .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
                assertThat(iconContentDescriptionId).isEqualTo(faceIconAuthingDescription)
                assertThat(shouldAnimateIconView).isEqualTo(true)
            } else if ((testCase.isCoex && forceExplicitFlow) || testCase.isFingerprintOnly) {
                // Fingerprint-only or explicit co-ex auth
                if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
                    assertThat(iconAsset).isEqualTo(getSfpsAsset_fingerprintAuthenticating())
                    assertThat(iconContentDescriptionId)
                        .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
                    assertThat(iconContentDescriptionId).isEqualTo(sfpsFindSensorDescription)
                    assertThat(shouldAnimateIconView).isEqualTo(true)
                } else {
                    assertThat(iconAsset)
                        .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
                    assertThat(iconContentDescriptionId)
                        .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
                    assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription)
                    assertThat(shouldAnimateIconView).isEqualTo(false)
                }
            }
@@ -397,26 +410,25 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
        if (testCase.isFaceOnly) {
            // Face-only auth
            assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_error)
            assertThat(iconContentDescriptionId).isEqualTo(R.string.keyguard_face_failed)
            assertThat(iconContentDescriptionId).isEqualTo(faceFailedDescription)
            assertThat(shouldAnimateIconView).isEqualTo(true)

            // Clear error, go to idle
            errorJob.join()

            assertThat(iconAsset).isEqualTo(R.raw.face_dialog_error_to_idle)
            assertThat(iconContentDescriptionId)
                .isEqualTo(R.string.biometric_dialog_face_icon_description_idle)
            assertThat(iconContentDescriptionId).isEqualTo(faceIconIdleDescription)
            assertThat(shouldAnimateIconView).isEqualTo(true)
        } else if ((testCase.isCoex && forceExplicitFlow) || testCase.isFingerprintOnly) {
            // Fingerprint-only or explicit co-ex auth
            if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
                assertThat(iconAsset).isEqualTo(getSfpsAsset_fingerprintToError())
                assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
                assertThat(iconContentDescriptionId).isEqualTo(bpTryAgainDescription)
                assertThat(shouldAnimateIconView).isEqualTo(true)
            } else {
                assertThat(iconAsset)
                    .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
                assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
                assertThat(iconContentDescriptionId).isEqualTo(bpTryAgainDescription)
                assertThat(shouldAnimateIconView).isEqualTo(true)
            }

@@ -425,14 +437,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa

            if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
                assertThat(iconAsset).isEqualTo(getSfpsAsset_errorToFingerprint())
                assertThat(iconContentDescriptionId)
                    .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
                assertThat(iconContentDescriptionId).isEqualTo(sfpsFindSensorDescription)
                assertThat(shouldAnimateIconView).isEqualTo(true)
            } else {
                assertThat(iconAsset)
                    .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
                assertThat(iconContentDescriptionId)
                    .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
                assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription)
                assertThat(shouldAnimateIconView).isEqualTo(true)
            }
        }
@@ -472,13 +482,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
                    // Covers (1) fingerprint-only (2) co-ex, authenticated by fingerprint
                    if (testCase.authenticatedByFingerprint) {
                        assertThat(iconAsset).isEqualTo(R.raw.biometricprompt_sfps_error_to_success)
                        assertThat(iconContentDescriptionId)
                            .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
                        assertThat(iconContentDescriptionId).isEqualTo(sfpsFindSensorDescription)
                        assertThat(shouldAnimateIconView).isEqualTo(true)
                    } else { // Covers co-ex, authenticated by face
                        assertThat(iconAsset).isEqualTo(R.raw.biometricprompt_sfps_error_to_unlock)
                        assertThat(iconContentDescriptionId)
                            .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
                            .isEqualTo(fingerprintIconAuthenticatedDescription)
                        assertThat(shouldAnimateIconView).isEqualTo(true)

                        // Confirm authentication
@@ -486,8 +495,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa

                        assertThat(iconAsset)
                            .isEqualTo(R.raw.biometricprompt_sfps_unlock_to_success)
                        assertThat(iconContentDescriptionId)
                            .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
                        assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription)
                        assertThat(shouldAnimateIconView).isEqualTo(true)
                    }
                } else { // Non-SFPS (UDFPS / rear-FPS) test cases
@@ -495,14 +503,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
                    if (testCase.authenticatedByFingerprint) {
                        assertThat(iconAsset)
                            .isEqualTo(R.raw.fingerprint_dialogue_error_to_success_lottie)
                        assertThat(iconContentDescriptionId)
                            .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
                        assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription)
                        assertThat(shouldAnimateIconView).isEqualTo(true)
                    } else { //  co-ex, authenticated by face
                        assertThat(iconAsset)
                            .isEqualTo(R.raw.fingerprint_dialogue_error_to_unlock_lottie)
                        assertThat(iconContentDescriptionId)
                            .isEqualTo(R.string.biometric_dialog_confirm)
                        assertThat(iconContentDescriptionId).isEqualTo(bpConfirmDescription)
                        assertThat(shouldAnimateIconView).isEqualTo(true)

                        // Confirm authentication
@@ -512,8 +518,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
                            .isEqualTo(
                                R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
                            )
                        assertThat(iconContentDescriptionId)
                            .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
                        assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription)
                        assertThat(shouldAnimateIconView).isEqualTo(true)
                    }
                }
@@ -543,22 +548,19 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
                    // Fingerprint icon asset assertions
                    if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
                        assertThat(iconAsset).isEqualTo(getSfpsAsset_fingerprintToSuccess())
                        assertThat(iconContentDescriptionId)
                            .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
                        assertThat(iconContentDescriptionId).isEqualTo(sfpsFindSensorDescription)
                        assertThat(shouldAnimateIconView).isEqualTo(true)
                    } else {
                        assertThat(iconAsset)
                            .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie)
                        assertThat(iconContentDescriptionId)
                            .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
                        assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription)
                        assertThat(shouldAnimateIconView).isEqualTo(true)
                    }
                } else if (testCase.isFaceOnly || testCase.isCoex) {
                    // Face icon asset assertions
                    // If co-ex, use implicit flow (explicit flow always requires confirmation)
                    assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark)
                    assertThat(iconContentDescriptionId)
                        .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
                    assertThat(iconContentDescriptionId).isEqualTo(faceIconAuthedDescription)
                    assertThat(shouldAnimateIconView).isEqualTo(true)
                    assertThat(message).isEqualTo(PromptMessage.Empty)
                }
@@ -586,20 +588,18 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa

                if (testCase.isFaceOnly) {
                    assertThat(iconAsset).isEqualTo(R.raw.face_dialog_wink_from_dark)
                    assertThat(iconContentDescriptionId)
                        .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
                    assertThat(iconContentDescriptionId).isEqualTo(faceIconAuthedDescription)
                    assertThat(shouldAnimateIconView).isEqualTo(true)
                } else if (testCase.isCoex) { // explicit flow, confirmation requested
                    if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
                        assertThat(iconAsset).isEqualTo(getSfpsAsset_fingerprintToUnlock())
                        assertThat(iconContentDescriptionId)
                            .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
                            .isEqualTo(fingerprintIconAuthenticatedDescription)
                        assertThat(shouldAnimateIconView).isEqualTo(true)
                    } else {
                        assertThat(iconAsset)
                            .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie)
                        assertThat(iconContentDescriptionId)
                            .isEqualTo(R.string.biometric_dialog_confirm)
                        assertThat(iconContentDescriptionId).isEqualTo(bpConfirmDescription)
                        assertThat(shouldAnimateIconView).isEqualTo(true)
                    }
                }
@@ -628,8 +628,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa

                if (testCase.isFaceOnly) {
                    assertThat(iconAsset).isEqualTo(R.raw.face_dialog_dark_to_checkmark)
                    assertThat(iconContentDescriptionId)
                        .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
                    assertThat(iconContentDescriptionId).isEqualTo(faceIconConfirmedDescription)
                    assertThat(shouldAnimateIconView).isEqualTo(true)
                }

@@ -644,8 +643,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
                            .isEqualTo(
                                R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
                            )
                        assertThat(iconContentDescriptionId)
                            .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
                        assertThat(iconContentDescriptionId).isEqualTo(udfpsIconDescription)
                        assertThat(shouldAnimateIconView).isEqualTo(true)
                    }
                }
+22 −38
Original line number Diff line number Diff line
@@ -43,7 +43,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieCompositionFactory
import com.android.systemui.Flags.bpIconA11y
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.biometrics.Utils.ellipsize
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -63,7 +63,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import com.android.app.tracing.coroutines.launchTraced as launch

private const val TAG = "BiometricViewBinder"

@@ -327,30 +326,29 @@ object BiometricViewBinder {

                // reuse the icon as a confirm button
                launch {
                    if (bpIconA11y()) {
                    viewModel.isIconConfirmButton.collect { isButton ->
                            if (isButton) {
                        if (isButton && !accessibilityManager.isEnabled) {
                            iconView.onTouchListener { _: View, event: MotionEvent ->
                                viewModel.onOverlayTouch(event)
                            }
                                iconView.setOnClickListener { viewModel.confirmAuthenticated() }
                        } else {
                            iconView.setOnTouchListener(null)
                                iconView.setOnClickListener(null)
                        }
                    }
                    } else {
                        viewModel.isIconConfirmButton
                            .map { isPending ->
                                when {
                                    isPending && modalities.hasFaceAndFingerprint ->
                                        View.OnTouchListener { _: View, event: MotionEvent ->
                                            viewModel.onOverlayTouch(event)
                                        }
                                    else -> null
                }

                launch {
                    combine(viewModel.isIconConfirmButton, viewModel.isAuthenticated, ::Pair)
                        .collect { (isIconConfirmButton, authState) ->
                            // Only use the icon as a button for talkback when coex and pending
                            // confirmation
                            if (
                                accessibilityManager.isEnabled &&
                                    isIconConfirmButton &&
                                    authState.isAuthenticated
                            ) {
                                iconView.setOnClickListener { viewModel.confirmAuthenticated() }
                            }
                            .collect { onTouch -> iconView.setOnTouchListener(onTouch) }
                        }
                }

@@ -365,22 +363,8 @@ object BiometricViewBinder {
                            backgroundView.setOnClickListener(null)
                            backgroundView.importantForAccessibility =
                                IMPORTANT_FOR_ACCESSIBILITY_NO

                            // Allow icon to be used as confirmation button with udfps and a11y
                            // enabled
                            if (
                                !bpIconA11y() &&
                                    accessibilityManager.isTouchExplorationEnabled &&
                                    modalities.hasUdfps
                            ) {
                                iconView.setOnClickListener { viewModel.confirmAuthenticated() }
                            }
                        }
                        if (authState.isAuthenticatedAndConfirmed) {
                            view.announceForAccessibility(
                                view.resources.getString(R.string.biometric_dialog_authenticated)
                            )

                            launch {
                                delay(authState.delay)
                                if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+33 −33

File changed.

Preview size limit exceeded, changes collapsed.