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

Commit cb46bcef authored by Austin Delgado's avatar Austin Delgado
Browse files

Add UDFPS talkback directional guidance for BP

Test: manual, talkback announces direction to move
Test: atest PromptViewModelTest
Bug: 310044658
Flag: ACONFIG bp_talkback DEVELOPMENT
Change-Id: Ifd91dfad63189709cdb5881dd57f70a1723e002c
parent fce39f14
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -37,6 +37,10 @@ data class BiometricModalities(
    val hasSfps: Boolean
        get() = hasFingerprint && fingerprintProperties!!.isAnySidefpsType

    /** If UDFPS authentication is available. */
    val hasUdfps: Boolean
        get() = hasFingerprint && fingerprintProperties!!.isAnyUdfpsType

    /** If fingerprint authentication is available (and [faceProperties] is non-null). */
    val hasFace: Boolean
        get() = faceProperties != null
+16 −0
Original line number Diff line number Diff line
@@ -376,6 +376,22 @@ object BiometricViewBinder {
                    }
                }

                // Talkback directional guidance
                backgroundView.setOnHoverListener { _, event ->
                    launch {
                        viewModel.onAnnounceAccessibilityHint(
                            event,
                            accessibilityManager.isTouchExplorationEnabled
                        )
                    }
                    false
                }
                launch {
                    viewModel.accessibilityHint.collect { message ->
                        if (message.isNotBlank()) view.announceForAccessibility(message)
                    }
                }

                // Play haptics
                launch {
                    viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
+47 −1
Original line number Diff line number Diff line
@@ -21,9 +21,12 @@ import android.hardware.biometrics.BiometricPrompt
import android.util.Log
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import com.android.systemui.Flags.bpTalkback
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.DisplayRotation
@@ -35,7 +38,9 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -49,7 +54,9 @@ class PromptViewModel
constructor(
    displayStateInteractor: DisplayStateInteractor,
    promptSelectorInteractor: PromptSelectorInteractor,
    @Application context: Context,
    @Application private val context: Context,
    private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
    private val udfpsUtils: UdfpsUtils
) {
    /** The set of modalities available for this prompt */
    val modalities: Flow<BiometricModalities> =
@@ -69,6 +76,11 @@ constructor(
    val faceIconHeight: Int =
        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)

    private val _accessibilityHint = MutableSharedFlow<String>()

    /** Hint for talkback directional guidance */
    val accessibilityHint: Flow<String> = _accessibilityHint.asSharedFlow()

    private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)

    /** If the user is currently authenticating (i.e. at least one biometric is scanning). */
@@ -516,6 +528,40 @@ constructor(
        return false
    }

    /** Sets the message used for UDFPS directional guidance */
    suspend fun onAnnounceAccessibilityHint(
        event: MotionEvent,
        touchExplorationEnabled: Boolean,
    ): Boolean {
        if (bpTalkback() && modalities.first().hasUdfps && touchExplorationEnabled) {
            // TODO(b/315184924): Remove uses of UdfpsUtils
            val scaledTouch =
                udfpsUtils.getTouchInNativeCoordinates(
                    event.getPointerId(0),
                    event,
                    udfpsOverlayInteractor.udfpsOverlayParams.value
                )
            if (
                !udfpsUtils.isWithinSensorArea(
                    event.getPointerId(0),
                    event,
                    udfpsOverlayInteractor.udfpsOverlayParams.value
                )
            ) {
                _accessibilityHint.emit(
                    udfpsUtils.onTouchOutsideOfSensorArea(
                        touchExplorationEnabled,
                        context,
                        scaledTouch.x,
                        scaledTouch.y,
                        udfpsOverlayInteractor.udfpsOverlayParams.value
                    )
                )
            }
        }
        return false
    }

    /**
     * Switch to the credential view.
     *
+17 −0
Original line number Diff line number Diff line
@@ -47,12 +47,14 @@ import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorI
import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.events.ANIMATING_OUT
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -101,6 +103,12 @@ open class AuthContainerViewTest : SysuiTestCase() {
    lateinit var interactionJankMonitor: InteractionJankMonitor
    @Mock
    lateinit var vibrator: VibratorHelper
    @Mock
    lateinit var udfpsUtils: UdfpsUtils
    @Mock
    lateinit var authController: AuthController
    @Mock
    lateinit var selectedUserInteractor: SelectedUserInteractor

    private val testScope = TestScope(StandardTestDispatcher())
    private val fakeExecutor = FakeExecutor(FakeSystemClock())
@@ -123,6 +131,7 @@ open class AuthContainerViewTest : SysuiTestCase() {

    private lateinit var displayRepository: FakeDisplayRepository
    private lateinit var displayStateInteractor: DisplayStateInteractor
    private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor

    private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)

@@ -140,6 +149,12 @@ open class AuthContainerViewTest : SysuiTestCase() {
                    displayStateRepository,
                    displayRepository,
            )
        udfpsOverlayInteractor =
                UdfpsOverlayInteractor(
                        authController,
                        selectedUserInteractor,
                        testScope.backgroundScope,
                )
    }

    @After
@@ -532,6 +547,8 @@ open class AuthContainerViewTest : SysuiTestCase() {
            displayStateInteractor,
            promptSelectorInteractor,
            context,
            udfpsOverlayInteractor,
            udfpsUtils
        ),
        { credentialViewModel },
        Handler(TestableLooper.get(this).looper),
+41 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.biometrics.shared.model

import android.hardware.fingerprint.FingerprintSensorProperties
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.faceSensorPropertiesInternal
@@ -34,6 +35,46 @@ class BiometricModalitiesTest : SysuiTestCase() {
        assertThat(BiometricModalities().isEmpty).isTrue()
    }

    @Test
    fun hasUdfps() {
        with(
            BiometricModalities(
                fingerprintProperties = fingerprintSensorPropertiesInternal(
                    sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
                ).first(),
            )
        ) {
            assertThat(isEmpty).isFalse()
            assertThat(hasUdfps).isTrue()
            assertThat(hasSfps).isFalse()
            assertThat(hasFace).isFalse()
            assertThat(hasFaceOnly).isFalse()
            assertThat(hasFingerprint).isTrue()
            assertThat(hasFingerprintOnly).isTrue()
            assertThat(hasFaceAndFingerprint).isFalse()
        }
    }

    @Test
    fun hasSfps() {
        with(
            BiometricModalities(
                fingerprintProperties = fingerprintSensorPropertiesInternal(
                    sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
                ).first(),
            )
        ) {
            assertThat(isEmpty).isFalse()
            assertThat(hasUdfps).isFalse()
            assertThat(hasSfps).isTrue()
            assertThat(hasFace).isFalse()
            assertThat(hasFaceOnly).isFalse()
            assertThat(hasFingerprint).isTrue()
            assertThat(hasFingerprintOnly).isTrue()
            assertThat(hasFaceAndFingerprint).isFalse()
        }
    }

    @Test
    fun fingerprintOnly() {
        with(
Loading