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

Commit c002a879 authored by Joe Bolinger's avatar Joe Bolinger
Browse files

Add unit tests for BiometricPrompt implementation with class 3 support.

Fork AuthContainerViewTest test to run agains both new / legacy prompt.

Bug: 272510026
Bug: 251476085
Test: atest com.android.systemui.biometrics
Test: manual (use BP integration test app and perform auth with all variations fp-only, face-only, coex)

Change-Id: I2972eecceb27662cad1bb718639a52f61678fcbc
parent aa1922ac
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -73,7 +73,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {

    @Test
    fun fingerprintSuccessDoesNotRequireExplicitConfirmation() {
        biometricView.onDialogAnimatedIn()
        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
        biometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT)
        TestableLooper.get(this).moveTimeForward(1000)
        waitForIdleSync()
@@ -84,7 +84,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {

    @Test
    fun faceSuccessRequiresExplicitConfirmation() {
        biometricView.onDialogAnimatedIn()
        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
        biometricView.onAuthenticationSucceeded(TYPE_FACE)
        waitForIdleSync()

@@ -104,7 +104,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {

    @Test
    fun ignoresFaceErrors_faceIsNotClass3_notLockoutError() {
        biometricView.onDialogAnimatedIn()
        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
        biometricView.onError(TYPE_FACE, "not a face")
        waitForIdleSync()

@@ -121,7 +121,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
    @Test
    fun doNotIgnoresFaceErrors_faceIsClass3_notLockoutError() {
        biometricView.isFaceClass3 = true
        biometricView.onDialogAnimatedIn()
        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
        biometricView.onError(TYPE_FACE, "not a face")
        waitForIdleSync()

@@ -138,7 +138,7 @@ class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
    @Test
    fun doNotIgnoresFaceErrors_faceIsClass3_lockoutError() {
        biometricView.isFaceClass3 = true
        biometricView.onDialogAnimatedIn()
        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
        biometricView.onError(
            TYPE_FACE,
            FaceManager.getErrorString(
+2 −2
Original line number Diff line number Diff line
@@ -120,7 +120,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {

    @Test
    fun testNegativeButton_beforeAuthentication_sendsActionButtonNegative() {
        biometricView.onDialogAnimatedIn()
        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
        biometricView.mNegativeButton.performClick()
        TestableLooper.get(this).moveTimeForward(1000)
        waitForIdleSync()
@@ -212,7 +212,7 @@ class AuthBiometricFingerprintViewTest : SysuiTestCase() {
    @Test
    fun testIgnoresUselessHelp() {
        biometricView.mAnimationDurationHideDialog = 10_000
        biometricView.onDialogAnimatedIn()
        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
        waitForIdleSync()

        assertThat(biometricView.isAuthenticating).isTrue()
+42 −12
Original line number Diff line number Diff line
@@ -41,11 +41,15 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakePromptRepository
import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
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.ui.viewmodel.AuthBiometricFingerprintViewModel
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -53,29 +57,34 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever

@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
class AuthContainerViewTest : SysuiTestCase() {
open class AuthContainerViewTest : SysuiTestCase() {

    @JvmField @Rule
    var mockitoRule = MockitoJUnit.rule()

    private val featureFlags = FakeFeatureFlags()

    @Mock
    lateinit var callback: AuthDialogCallback
    @Mock
@@ -91,16 +100,25 @@ class AuthContainerViewTest : SysuiTestCase() {
    @Mock
    lateinit var interactionJankMonitor: InteractionJankMonitor

    // TODO(b/278622168): remove with flag
    open val useNewBiometricPrompt = false

    private val testScope = TestScope(StandardTestDispatcher())
    private val fakeExecutor = FakeExecutor(FakeSystemClock())
    private val biometricPromptRepository = FakePromptRepository()
    private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
    private val credentialInteractor = FakeCredentialInteractor()
    private val bpCredentialInteractor = BiometricPromptCredentialInteractor(
    private val bpCredentialInteractor = PromptCredentialInteractor(
        Dispatchers.Main.immediate,
        biometricPromptRepository,
        credentialInteractor
        credentialInteractor,
    )
    private val promptSelectorInteractor by lazy {
        PromptSelectorInteractorImpl(
            biometricPromptRepository,
            lockPatternUtils,
        )
    }
    private val displayStateInteractor = DisplayStateInteractorImpl(
        testScope.backgroundScope,
        mContext,
@@ -115,6 +133,11 @@ class AuthContainerViewTest : SysuiTestCase() {

    private var authContainer: TestAuthContainerView? = null

    @Before
    fun setup() {
        featureFlags.set(Flags.BIOMETRIC_BP_STRONG, useNewBiometricPrompt)
    }

    @After
    fun tearDown() {
        if (authContainer?.isAttachedToWindow == true) {
@@ -125,7 +148,7 @@ class AuthContainerViewTest : SysuiTestCase() {
    @Test
    fun testNotifiesAnimatedIn() {
        initializeFingerprintContainer()
        verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L)
        verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
    }

    @Test
@@ -164,13 +187,13 @@ class AuthContainerViewTest : SysuiTestCase() {
        container.dismissFromSystemServer()
        waitForIdleSync()

        verify(callback, never()).onDialogAnimatedIn(anyLong())
        verify(callback, never()).onDialogAnimatedIn(anyLong(), anyBoolean())

        container.addToView()
        waitForIdleSync()

        // attaching the view resets the state and allows this to happen again
        verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L)
        verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
    }

    @Test
@@ -185,7 +208,7 @@ class AuthContainerViewTest : SysuiTestCase() {

        // the first time is triggered by initializeFingerprintContainer()
        // the second time was triggered by dismissWithoutCallback()
        verify(callback, times(2)).onDialogAnimatedIn(authContainer?.requestId ?: 0L)
        verify(callback, times(2)).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
    }

    @Test
@@ -479,6 +502,8 @@ class AuthContainerViewTest : SysuiTestCase() {
                this.authenticators = authenticators
            }
        },
        featureFlags,
        testScope.backgroundScope,
        fingerprintProps,
        faceProps,
        wakefulnessLifecycle,
@@ -486,8 +511,10 @@ class AuthContainerViewTest : SysuiTestCase() {
        userManager,
        lockPatternUtils,
        interactionJankMonitor,
        { bpCredentialInteractor },
        { authBiometricFingerprintViewModel },
        { promptSelectorInteractor },
        { bpCredentialInteractor },
        PromptViewModel(promptSelectorInteractor),
        { credentialViewModel },
        Handler(TestableLooper.get(this).looper),
        fakeExecutor
@@ -497,7 +524,10 @@ class AuthContainerViewTest : SysuiTestCase() {
        }
    }

    override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages()
    override fun waitForIdleSync() {
        testScope.runCurrent()
        TestableLooper.get(this).processAllMessages()
    }

    private fun AuthContainerView.addToView() {
        ViewUtils.attachView(this)
+30 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.biometrics

import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import org.junit.runner.RunWith

// TODO(b/278622168): remove with flag
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class AuthContainerViewTest2 : AuthContainerViewTest() {
    override val useNewBiometricPrompt = true
}
+24 −13
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.systemui.biometrics;

import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;

import static com.google.common.truth.Truth.assertThat;

@@ -54,7 +53,6 @@ import android.content.res.Resources;
import android.graphics.Point;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.ComponentInfoInternal;
@@ -91,10 +89,14 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.udfps.UdfpsUtils;
import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel;
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.VibratorHelper;
@@ -171,12 +173,16 @@ public class AuthControllerTest extends SysuiTestCase {
    @Mock
    private InteractionJankMonitor mInteractionJankMonitor;
    @Mock
    private BiometricPromptCredentialInteractor mBiometricPromptCredentialInteractor;
    private PromptCredentialInteractor mBiometricPromptCredentialInteractor;
    @Mock
    private PromptSelectorInteractor mPromptSelectionInteractor;
    @Mock
    private AuthBiometricFingerprintViewModel mAuthBiometricFingerprintViewModel;
    @Mock
    private CredentialViewModel mCredentialViewModel;
    @Mock
    private PromptViewModel mPromptViewModel;
    @Mock
    private UdfpsUtils mUdfpsUtils;

    @Captor
@@ -194,12 +200,17 @@ public class AuthControllerTest extends SysuiTestCase {
    private Handler mHandler;
    private DelayableExecutor mBackgroundExecutor;
    private TestableAuthController mAuthController;
    private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();

    @Mock
    private VibratorHelper mVibratorHelper;

    @Before
    public void setup() throws RemoteException {
        // TODO(b/278622168): remove with flag
        // AuthController simply passes this through to AuthContainerView (does not impact test)
        mFeatureFlags.set(Flags.BIOMETRIC_BP_STRONG, false);

        mContextSpy = spy(mContext);
        mExecution = new FakeExecution();
        mTestableLooper = TestableLooper.get(this);
@@ -952,8 +963,7 @@ public class AuthControllerTest extends SysuiTestCase {
                0 /* userId */,
                0 /* operationId */,
                "testPackage",
                REQUEST_ID,
                BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE);
                REQUEST_ID);
    }

    private void switchTask(String packageName) {
@@ -993,25 +1003,26 @@ public class AuthControllerTest extends SysuiTestCase {
        private PromptInfo mLastBiometricPromptInfo;

        TestableAuthController(Context context) {
            super(context, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
            super(context, mFeatureFlags, null /* applicationCoroutineScope */,
                    mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
                    mFingerprintManager, mFaceManager, () -> mUdfpsController,
                    () -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle,
                    mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger,
                    mLogContextInteractor, () -> mBiometricPromptCredentialInteractor,
                    () -> mAuthBiometricFingerprintViewModel, () -> mCredentialViewModel,
                    mInteractionJankMonitor, mHandler, mBackgroundExecutor, mVibratorHelper,
                    mUdfpsUtils);
                    mLogContextInteractor, () -> mAuthBiometricFingerprintViewModel,
                    () -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
                    () -> mCredentialViewModel, () -> mPromptViewModel,
                    mInteractionJankMonitor, mHandler,
                    mBackgroundExecutor, mVibratorHelper, mUdfpsUtils);
        }

        @Override
        protected AuthDialog buildDialog(DelayableExecutor bgExecutor, PromptInfo promptInfo,
                boolean requireConfirmation, int userId, int[] sensorIds,
                String opPackageName, boolean skipIntro, long operationId, long requestId,
                @BiometricManager.BiometricMultiSensorMode int multiSensorConfig,
                WakefulnessLifecycle wakefulnessLifecycle,
                AuthDialogPanelInteractionDetector panelInteractionDetector,
                UserManager userManager,
                LockPatternUtils lockPatternUtils) {
                LockPatternUtils lockPatternUtils, PromptViewModel viewModel) {

            mLastBiometricPromptInfo = promptInfo;

Loading