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 Diff line number Diff line
@@ -44,6 +44,7 @@ import android.widget.ScrollView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
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_PIN
import com.android.internal.widget.lockPatternUtils
@@ -113,6 +114,8 @@ open class AuthContainerViewTest : SysuiTestCase() {
        whenever(packageManager.getPackageInfo(any(String::class.java), anyInt()))
            .thenReturn(PackageInfo())
        context.setMockPackageManager(packageManager)
        whenever(lockPatternUtils.getCredentialTypeForUser(anyInt()))
            .thenReturn(CREDENTIAL_TYPE_PASSWORD)
    }

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

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

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

        assertThat(currentPrompt).isNull()

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

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

            setPrompt(onSwitchToCredential = true)

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

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

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

            setPrompt(info)

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

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

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

            setPrompt(info)

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

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

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

            setPrompt(info)

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

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

        // not using biometrics, should be null with no fallback option
        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)
        }

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

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

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

        final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        final PromptKind kind = mPromptViewModel.getPromptKind().getValue();
@@ -364,10 +365,16 @@ public class AuthContainerView extends LinearLayout
                mLayout = (ConstraintLayout) layoutInflater.inflate(
                        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 {
            mLayout = (FrameLayout) layoutInflater.inflate(
                    R.layout.auth_container_view, this, false /* attachToRoot */);
        }

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

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

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

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

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

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

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

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

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

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

    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 ->
                if (prompt != null && isAllowed) {
                    getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId)
@@ -243,6 +260,7 @@ constructor(
                    PromptKind.None
                }
            }
        }

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

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

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

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

Loading