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

Commit 8354004b authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Improve biometric prompt view transitions" into main

parents d61187a4 7ac60153
Loading
Loading
Loading
Loading
+3 −0
Original line number Original line Diff line number Diff line
@@ -44,6 +44,7 @@ import android.widget.ScrollView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN
import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN
import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN
import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN
import com.android.internal.widget.lockPatternUtils
import com.android.internal.widget.lockPatternUtils
@@ -113,6 +114,8 @@ open class AuthContainerViewTest : SysuiTestCase() {
        whenever(packageManager.getPackageInfo(any(String::class.java), anyInt()))
        whenever(packageManager.getPackageInfo(any(String::class.java), anyInt()))
            .thenReturn(PackageInfo())
            .thenReturn(PackageInfo())
        context.setMockPackageManager(packageManager)
        context.setMockPackageManager(packageManager)
        whenever(lockPatternUtils.getCredentialTypeForUser(anyInt()))
            .thenReturn(CREDENTIAL_TYPE_PASSWORD)
    }
    }


    @After
    @After
+34 −7
Original line number Original line Diff line number Diff line
@@ -16,16 +16,19 @@


package com.android.systemui.biometrics.domain.interactor
package com.android.systemui.biometrics.domain.interactor


import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.ComponentName
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.BiometricManager.Authenticators
import android.hardware.biometrics.BiometricManager.Authenticators
import android.hardware.biometrics.Flags
import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.PromptVerticalListContentView
import android.hardware.biometrics.PromptVerticalListContentView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN
import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
@@ -172,6 +175,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() {
        val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed)
        val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed)
        val credentialKind by collectLastValue(interactor.credentialKind)
        val credentialKind by collectLastValue(interactor.credentialKind)
        val isConfirmationRequired by collectLastValue(interactor.isConfirmationRequired)
        val isConfirmationRequired by collectLastValue(interactor.isConfirmationRequired)
        val currentView by collectLastValue(interactor.currentView)


        assertThat(currentPrompt).isNull()
        assertThat(currentPrompt).isNull()


@@ -192,6 +196,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() {
        assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE)
        assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE)
        assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT)
        assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT)
        assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME)
        assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME)
        assertThat(currentView).isEqualTo(BiometricPromptView.BIOMETRIC)
        assertThat(promptKind!!.isBiometric()).isTrue()
        assertThat(promptKind!!.isBiometric()).isTrue()
        assertThat(currentPrompt?.componentNameForConfirmDeviceCredentialActivity)
        assertThat(currentPrompt?.componentNameForConfirmDeviceCredentialActivity)
            .isEqualTo(
            .isEqualTo(
@@ -203,8 +208,12 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() {
        if (allowCredentialFallback) {
        if (allowCredentialFallback) {
            assertThat(credentialKind).isSameInstanceAs(PromptKind.Password)
            assertThat(credentialKind).isSameInstanceAs(PromptKind.Password)
            assertThat(isCredentialAllowed).isTrue()
            assertThat(isCredentialAllowed).isTrue()
        } else {
            if (Flags.bpFallbackOptions()) {
                assertThat(credentialKind).isEqualTo(PromptKind.Password)
            } else {
            } else {
                assertThat(credentialKind).isEqualTo(PromptKind.None)
                assertThat(credentialKind).isEqualTo(PromptKind.None)
            }
            assertThat(isCredentialAllowed).isFalse()
            assertThat(isCredentialAllowed).isFalse()
        }
        }
        assertThat(isConfirmationRequired).isEqualTo(confirmationRequired)
        assertThat(isConfirmationRequired).isEqualTo(confirmationRequired)
@@ -282,11 +291,13 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() {
            setUserCredentialType(isPassword = true)
            setUserCredentialType(isPassword = true)


            val promptKind by collectLastValue(interactor.promptKind)
            val promptKind by collectLastValue(interactor.promptKind)
            val currentView by collectLastValue(interactor.currentView)
            assertThat(promptKind).isEqualTo(PromptKind.None)
            assertThat(promptKind).isEqualTo(PromptKind.None)


            setPrompt(onSwitchToCredential = true)
            setPrompt(onSwitchToCredential = true)


            assertThat(promptKind).isEqualTo(PromptKind.Password)
            assertThat(promptKind).isEqualTo(PromptKind.Password)
            assertThat(currentView).isEqualTo(BiometricPromptView.CREDENTIAL)


            interactor.resetPrompt(REQUEST_ID)
            interactor.resetPrompt(REQUEST_ID)
            verifyUnset()
            verifyUnset()
@@ -303,11 +314,13 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() {
                }
                }


            val promptKind by collectLastValue(interactor.promptKind)
            val promptKind by collectLastValue(interactor.promptKind)
            val currentView by collectLastValue(interactor.currentView)
            assertThat(promptKind).isEqualTo(PromptKind.None)
            assertThat(promptKind).isEqualTo(PromptKind.None)


            setPrompt(info)
            setPrompt(info)


            assertThat(promptKind).isEqualTo(PromptKind.Password)
            assertThat(promptKind).isEqualTo(PromptKind.Password)
            assertThat(currentView).isEqualTo(BiometricPromptView.CREDENTIAL)


            interactor.resetPrompt(REQUEST_ID)
            interactor.resetPrompt(REQUEST_ID)
            verifyUnset()
            verifyUnset()
@@ -328,11 +341,13 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() {
                }
                }


            val promptKind by collectLastValue(interactor.promptKind)
            val promptKind by collectLastValue(interactor.promptKind)
            val currentView by collectLastValue(interactor.currentView)
            assertThat(promptKind).isEqualTo(PromptKind.None)
            assertThat(promptKind).isEqualTo(PromptKind.None)


            setPrompt(info)
            setPrompt(info)


            assertThat(promptKind).isEqualTo(PromptKind.Password)
            assertThat(promptKind).isEqualTo(PromptKind.Password)
            assertThat(currentView).isEqualTo(BiometricPromptView.CREDENTIAL)


            interactor.resetPrompt(REQUEST_ID)
            interactor.resetPrompt(REQUEST_ID)
            verifyUnset()
            verifyUnset()
@@ -351,11 +366,13 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() {
                }
                }


            val promptKind by collectLastValue(interactor.promptKind)
            val promptKind by collectLastValue(interactor.promptKind)
            val currentView by collectLastValue(interactor.currentView)
            assertThat(promptKind).isEqualTo(PromptKind.None)
            assertThat(promptKind).isEqualTo(PromptKind.None)


            setPrompt(info)
            setPrompt(info)


            assertThat(promptKind?.isOnePaneNoSensorLandscapeBiometric()).isTrue()
            assertThat(promptKind?.isOnePaneNoSensorLandscapeBiometric()).isTrue()
            assertThat(currentView).isEqualTo(BiometricPromptView.BIOMETRIC)


            interactor.resetPrompt(REQUEST_ID)
            interactor.resetPrompt(REQUEST_ID)
            verifyUnset()
            verifyUnset()
@@ -413,7 +430,17 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() {


        // not using biometrics, should be null with no fallback option
        // not using biometrics, should be null with no fallback option
        assertThat(currentPrompt).isNull()
        assertThat(currentPrompt).isNull()
        if (Flags.bpFallbackOptions()) {
            if (kind == PromptKind.Password) {
                assertThat(credentialKind).isEqualTo(PromptKind.Password)
            } else if (kind == PromptKind.Pin) {
                assertThat(credentialKind).isEqualTo(PromptKind.Pin)
            } else {
                assertThat(credentialKind).isEqualTo(PromptKind.Pattern)
            }
        } else {
            assertThat(credentialKind).isEqualTo(PromptKind.None)
            assertThat(credentialKind).isEqualTo(PromptKind.None)
        }


        interactor.resetPrompt(REQUEST_ID)
        interactor.resetPrompt(REQUEST_ID)
        verifyUnset()
        verifyUnset()
@@ -434,12 +461,12 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() {
    }
    }


    private fun setUserCredentialType(isPin: Boolean = false, isPassword: Boolean = false) {
    private fun setUserCredentialType(isPin: Boolean = false, isPassword: Boolean = false) {
        whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(any()))
        whenever(lockPatternUtils.getCredentialTypeForUser(any()))
            .thenReturn(
            .thenReturn(
                when {
                when {
                    isPin -> DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
                    isPin -> CREDENTIAL_TYPE_PIN
                    isPassword -> DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
                    isPassword -> CREDENTIAL_TYPE_PASSWORD
                    else -> DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
                    else -> CREDENTIAL_TYPE_PATTERN
                }
                }
            )
            )
    }
    }
+2 −2
Original line number Original line Diff line number Diff line
@@ -35,7 +35,7 @@ android:layout_height="match_parent">
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/rightGuideline"
        app:layout_constraintRight_toLeftOf="@+id/rightGuideline"
        app:layout_constraintLeft_toLeftOf="@+id/leftGuideline"
        app:layout_constraintLeft_toLeftOf="@+id/leftGuideline"
        app:layout_constraintTop_toTopOf="@+id/topGuideline" />
        app:layout_constraintTop_toTopOf="@+id/topBarrier" />


    <androidx.compose.ui.platform.ComposeView
    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/fallback_view"
        android:id="@+id/fallback_view"
@@ -45,7 +45,7 @@ android:layout_height="match_parent">
        android:fillViewport="true"
        android:fillViewport="true"
        app:layout_constrainedHeight="true"
        app:layout_constrainedHeight="true"
        app:layout_constrainedWidth="true"
        app:layout_constrainedWidth="true"
        app:layout_constraintBottom_toTopOf="@id/button_bar"
        app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
        app:layout_constraintEnd_toEndOf="@id/panel"
        app:layout_constraintEnd_toEndOf="@id/panel"
        app:layout_constraintStart_toStartOf="@id/panel"
        app:layout_constraintStart_toStartOf="@id/panel"
        app:layout_constraintTop_toTopOf="@+id/topGuideline"
        app:layout_constraintTop_toTopOf="@+id/topGuideline"
+8 −1
Original line number Original line Diff line number Diff line
@@ -350,9 +350,10 @@ public class AuthContainerView extends LinearLayout
        final boolean isLandscape = mContext.getResources().getConfiguration().orientation
        final boolean isLandscape = mContext.getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE;
                == Configuration.ORIENTATION_LANDSCAPE;
        mPromptSelectorInteractorProvider = promptSelectorInteractorProvider;
        mPromptSelectorInteractorProvider = promptSelectorInteractorProvider;
        // If the intro (animation) is being skipped, don't reset the prompt
        mPromptSelectorInteractorProvider.get().setPrompt(mConfig.mPromptInfo, mConfig.mUserId,
        mPromptSelectorInteractorProvider.get().setPrompt(mConfig.mPromptInfo, mConfig.mUserId,
                getRequestId(), biometricModalities, mConfig.mOperationId, mConfig.mOpPackageName,
                getRequestId(), biometricModalities, mConfig.mOperationId, mConfig.mOpPackageName,
                false /*onSwitchToCredential*/, isLandscape);
                false /*onSwitchToCredential*/, isLandscape, !mConfig.mSkipIntro);


        final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        final PromptKind kind = mPromptViewModel.getPromptKind().getValue();
        final PromptKind kind = mPromptViewModel.getPromptKind().getValue();
@@ -364,10 +365,16 @@ public class AuthContainerView extends LinearLayout
                mLayout = (ConstraintLayout) layoutInflater.inflate(
                mLayout = (ConstraintLayout) layoutInflater.inflate(
                        R.layout.biometric_prompt_one_pane_layout, this, false /* attachToRoot */);
                        R.layout.biometric_prompt_one_pane_layout, this, false /* attachToRoot */);
            }
            }

            // Setting visibility here to avoid unflagged layout changes
            if (Flags.bpFallbackOptions()) {
                mLayout.findViewById(R.id.auth_screen).setVisibility(View.GONE);
            }
        } else {
        } else {
            mLayout = (FrameLayout) layoutInflater.inflate(
            mLayout = (FrameLayout) layoutInflater.inflate(
                    R.layout.auth_container_view, this, false /* attachToRoot */);
                    R.layout.auth_container_view, this, false /* attachToRoot */);
        }
        }

        addView(mLayout);
        addView(mLayout);
        mBackgroundView = mLayout.findViewById(R.id.background);
        mBackgroundView = mLayout.findViewById(R.id.background);


+37 −11
Original line number Original line Diff line number Diff line
@@ -41,6 +41,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.domain.interactor.DisplayStateInteractor
import com.android.systemui.display.domain.interactor.DisplayStateInteractor
import com.android.systemui.display.shared.model.isDefaultOrientation
import com.android.systemui.display.shared.model.isDefaultOrientation
import com.android.systemui.kairos.awaitClose
import com.android.systemui.kairos.awaitClose
import com.android.systemui.util.kotlin.combine
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
@@ -72,6 +73,9 @@ interface PromptSelectorInteractor {
    /** The kind of prompt to use (biometric, pin, pattern, etc.). */
    /** The kind of prompt to use (biometric, pin, pattern, etc.). */
    val promptKind: StateFlow<PromptKind>
    val promptKind: StateFlow<PromptKind>


    /** The modalities available in the prompt */
    val modalities: Flow<BiometricModalities>

    /** If using a credential is allowed. */
    /** If using a credential is allowed. */
    val isCredentialAllowed: Flow<Boolean>
    val isCredentialAllowed: Flow<Boolean>


@@ -125,6 +129,7 @@ interface PromptSelectorInteractor {
        opPackageName: String,
        opPackageName: String,
        onSwitchToCredential: Boolean,
        onSwitchToCredential: Boolean,
        isLandscape: Boolean,
        isLandscape: Boolean,
        updateView: Boolean = true,
    )
    )


    /** Unset the current authentication request. */
    /** Unset the current authentication request. */
@@ -151,7 +156,8 @@ constructor(
            promptRepository.userId,
            promptRepository.userId,
            promptRepository.promptKind,
            promptRepository.promptKind,
            promptRepository.opPackageName,
            promptRepository.opPackageName,
        ) { promptInfo, challenge, userId, kind, opPackageName ->
            promptRepository.modalities,
        ) { promptInfo, challenge, userId, kind, opPackageName, modalities ->
            if (
            if (
                promptInfo == null || userId == null || challenge == null || opPackageName == null
                promptInfo == null || userId == null || challenge == null || opPackageName == null
            ) {
            ) {
@@ -171,7 +177,7 @@ constructor(
                        operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge),
                        operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge),
                        modalities =
                        modalities =
                            if (Flags.bpFallbackOptions()) {
                            if (Flags.bpFallbackOptions()) {
                                promptRepository.modalities.value
                                modalities
                            } else {
                            } else {
                                kind.activeModalities
                                kind.activeModalities
                            },
                            },
@@ -183,6 +189,8 @@ constructor(


    override val promptKind: StateFlow<PromptKind> = promptRepository.promptKind
    override val promptKind: StateFlow<PromptKind> = promptRepository.promptKind


    override val modalities: StateFlow<BiometricModalities> = promptRepository.modalities

    override val isConfirmationRequired: Flow<Boolean> =
    override val isConfirmationRequired: Flow<Boolean> =
        promptRepository.isConfirmationRequired.distinctUntilChanged()
        promptRepository.isConfirmationRequired.distinctUntilChanged()


@@ -236,6 +244,15 @@ constructor(
    override val fallbackOptions: Flow<List<FallbackOptionModel>> = promptRepository.fallbackOptions
    override val fallbackOptions: Flow<List<FallbackOptionModel>> = promptRepository.fallbackOptions


    override val credentialKind: Flow<PromptKind> =
    override val credentialKind: Flow<PromptKind> =
        if (Flags.bpFallbackOptions()) {
            promptRepository.userId.map { userId ->
                if (userId != null) {
                    getCredentialType(lockPatternUtils, userId)
                } else {
                    PromptKind.None
                }
            }
        } else {
            combine(prompt, isCredentialAllowed) { prompt, isAllowed ->
            combine(prompt, isCredentialAllowed) { prompt, isAllowed ->
                if (prompt != null && isAllowed) {
                if (prompt != null && isAllowed) {
                    getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId)
                    getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId)
@@ -243,6 +260,7 @@ constructor(
                    PromptKind.None
                    PromptKind.None
                }
                }
            }
            }
        }


    override val fingerprintSensorType: Flow<FingerprintSensorType> =
    override val fingerprintSensorType: Flow<FingerprintSensorType> =
        fingerprintPropertyRepository.sensorType
        fingerprintPropertyRepository.sensorType
@@ -298,6 +316,7 @@ constructor(
        opPackageName: String,
        opPackageName: String,
        onSwitchToCredential: Boolean,
        onSwitchToCredential: Boolean,
        isLandscape: Boolean,
        isLandscape: Boolean,
        updateView: Boolean,
    ) {
    ) {
        val effectiveUserId = credentialInteractor.getCredentialOwnerOrSelfId(userId)
        val effectiveUserId = credentialInteractor.getCredentialOwnerOrSelfId(userId)
        val hasCredentialViewShown = promptKind.value.isCredential()
        val hasCredentialViewShown = promptKind.value.isCredential()
@@ -309,11 +328,15 @@ constructor(
        val showBpWithoutIconForCredential = showBpForCredential && !hasCredentialViewShown
        val showBpWithoutIconForCredential = showBpForCredential && !hasCredentialViewShown
        var kind: PromptKind = PromptKind.None
        var kind: PromptKind = PromptKind.None


        if (onSwitchToCredential) {
        if (onSwitchToCredential || _currentView.value == BiometricPromptView.CREDENTIAL) {
            kind = getCredentialType(lockPatternUtils, effectiveUserId)
            kind = getCredentialType(lockPatternUtils, effectiveUserId)
            if (updateView) {
                _currentView.value = BiometricPromptView.CREDENTIAL
                _currentView.value = BiometricPromptView.CREDENTIAL
            }
        } else if (Utils.isBiometricAllowed(promptInfo) || showBpWithoutIconForCredential) {
        } else if (Utils.isBiometricAllowed(promptInfo) || showBpWithoutIconForCredential) {
            if (updateView) {
                _currentView.value = BiometricPromptView.BIOMETRIC
                _currentView.value = BiometricPromptView.BIOMETRIC
            }
            // TODO(b/330908557): Subscribe to
            // TODO(b/330908557): Subscribe to
            // displayStateInteractor.currentRotation.value.isDefaultOrientation() for checking
            // displayStateInteractor.currentRotation.value.isDefaultOrientation() for checking
            // `isLandscape` after removing AuthContainerView.
            // `isLandscape` after removing AuthContainerView.
@@ -332,7 +355,9 @@ constructor(
                    PromptKind.Biometric(modalities)
                    PromptKind.Biometric(modalities)
                }
                }
        } else if (isDeviceCredentialAllowed(promptInfo)) {
        } else if (isDeviceCredentialAllowed(promptInfo)) {
            if (updateView) {
                _currentView.value = BiometricPromptView.CREDENTIAL
                _currentView.value = BiometricPromptView.CREDENTIAL
            }
            kind = getCredentialType(lockPatternUtils, effectiveUserId)
            kind = getCredentialType(lockPatternUtils, effectiveUserId)
        }
        }


@@ -348,6 +373,7 @@ constructor(
    }
    }


    override fun resetPrompt(requestId: Long) {
    override fun resetPrompt(requestId: Long) {
        _currentView.value = BiometricPromptView.BIOMETRIC
        promptRepository.unsetPrompt(requestId)
        promptRepository.unsetPrompt(requestId)
    }
    }


Loading