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

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

Merge "Add UDFPS talkback directional guidance for BP" into main

parents bc6ca765 cb46bcef
Loading
Loading
Loading
Loading
+4 −0
Original line number Original line Diff line number Diff line
@@ -37,6 +37,10 @@ data class BiometricModalities(
    val hasSfps: Boolean
    val hasSfps: Boolean
        get() = hasFingerprint && fingerprintProperties!!.isAnySidefpsType
        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). */
    /** If fingerprint authentication is available (and [faceProperties] is non-null). */
    val hasFace: Boolean
    val hasFace: Boolean
        get() = faceProperties != null
        get() = faceProperties != null
+16 −0
Original line number Original line 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
                // Play haptics
                launch {
                launch {
                    viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
                    viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
+47 −1
Original line number Original line Diff line number Diff line
@@ -21,9 +21,12 @@ import android.hardware.biometrics.BiometricPrompt
import android.util.Log
import android.util.Log
import android.view.HapticFeedbackConstants
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
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.Utils
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
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.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.DisplayRotation
@@ -35,7 +38,9 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -49,7 +54,9 @@ class PromptViewModel
constructor(
constructor(
    displayStateInteractor: DisplayStateInteractor,
    displayStateInteractor: DisplayStateInteractor,
    promptSelectorInteractor: PromptSelectorInteractor,
    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 */
    /** The set of modalities available for this prompt */
    val modalities: Flow<BiometricModalities> =
    val modalities: Flow<BiometricModalities> =
@@ -69,6 +76,11 @@ constructor(
    val faceIconHeight: Int =
    val faceIconHeight: Int =
        context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
        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)
    private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)


    /** If the user is currently authenticating (i.e. at least one biometric is scanning). */
    /** If the user is currently authenticating (i.e. at least one biometric is scanning). */
@@ -516,6 +528,40 @@ constructor(
        return false
        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.
     * Switch to the credential view.
     *
     *
+17 −0
Original line number Original line 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.FakeCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
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.CredentialViewModel
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.events.ANIMATING_OUT
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.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertThat
@@ -101,6 +103,12 @@ open class AuthContainerViewTest : SysuiTestCase() {
    lateinit var interactionJankMonitor: InteractionJankMonitor
    lateinit var interactionJankMonitor: InteractionJankMonitor
    @Mock
    @Mock
    lateinit var vibrator: VibratorHelper
    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 testScope = TestScope(StandardTestDispatcher())
    private val fakeExecutor = FakeExecutor(FakeSystemClock())
    private val fakeExecutor = FakeExecutor(FakeSystemClock())
@@ -123,6 +131,7 @@ open class AuthContainerViewTest : SysuiTestCase() {


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


    private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
    private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)


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


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


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


import android.hardware.fingerprint.FingerprintSensorProperties
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.faceSensorPropertiesInternal
import com.android.systemui.biometrics.faceSensorPropertiesInternal
@@ -34,6 +35,46 @@ class BiometricModalitiesTest : SysuiTestCase() {
        assertThat(BiometricModalities().isEmpty).isTrue()
        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
    @Test
    fun fingerprintOnly() {
    fun fingerprintOnly() {
        with(
        with(
Loading