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

Commit ece79fdc authored by lbill's avatar lbill Committed by Bill Lin
Browse files

[DO NOT MERGE]Fix AuthCredentialPatternView#onErrorTimeoutFinish() bug

1. When user auth fail on pattern view many times, the pattern view
   would be locked and disabled and wait for 30s timeout.
   Once timeout finish invoke, re-enabled pattern view is necessary.

2. Add AuthCredentialViewTest.java to extend PIN/Password/Pattern
   unit test coverage.

Bug: 243699695
Test: atest com.android.systemui.biometrics.AuthCredentialViewTest
Test: manual setup Pattern lock, try to wrong auth many times
      device locked for 30s patter view disabled
      once timeout, re-enabled pattern view
Change-Id: I2fd26f6310e31e130d52a7c34a02a3ddf02b9166
parent b8a27eb6
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -120,7 +120,7 @@ public class AuthContainerView extends LinearLayout
    @VisibleForTesting final BiometricCallback mBiometricCallback;

    @Nullable private AuthBiometricView mBiometricView;
    @Nullable private AuthCredentialView mCredentialView;
    @VisibleForTesting @Nullable AuthCredentialView mCredentialView;
    private final AuthPanelController mPanelController;
    private final FrameLayout mFrameLayout;
    private final ImageView mBackgroundView;
+3 −4
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.content.Context;
import android.util.AttributeSet;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
@@ -34,7 +35,7 @@ import java.util.List;
 */
public class AuthCredentialPatternView extends AuthCredentialView {

    private LockPatternView mLockPatternView;
    @VisibleForTesting LockPatternView mLockPatternView;

    private class UnlockPatternListener implements LockPatternView.OnPatternListener {

@@ -93,9 +94,7 @@ public class AuthCredentialPatternView extends AuthCredentialView {
    @Override
    protected void onErrorTimeoutFinish() {
        super.onErrorTimeoutFinish();
        // select to enable marquee unless a screen reader is enabled
        mLockPatternView.setEnabled(!mAccessibilityManager.isEnabled()
                || !mAccessibilityManager.isTouchExplorationEnabled());
        mLockPatternView.setEnabled(true);
    }

    public AuthCredentialPatternView(Context context, AttributeSet attrs) {
+2 −1
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;

import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -98,7 +99,7 @@ public abstract class AuthCredentialView extends LinearLayout {
    protected int mUserId;
    protected long mOperationId;
    protected int mEffectiveUserId;
    protected ErrorTimer mErrorTimer;
    @VisibleForTesting ErrorTimer mErrorTimer;

    protected @Background DelayableExecutor mBackgroundExecutor;

+163 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.PromptInfo
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.Handler
import android.os.IBinder
import android.os.UserManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternView
import com.android.internal.widget.VerifyCredentialResponse
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthContainerView.BiometricCallback
import com.android.systemui.biometrics.AuthCredentialView.ErrorTimer
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit

@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class AuthCredentialViewTest : SysuiTestCase() {
    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()

    @Mock lateinit var callback: AuthDialogCallback
    @Mock lateinit var lockPatternUtils: LockPatternUtils
    @Mock lateinit var userManager: UserManager
    @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
    @Mock lateinit var windowToken: IBinder
    @Mock lateinit var interactionJankMonitor: InteractionJankMonitor

    private var authContainer: TestAuthContainerView? = null
    private var authCredentialView: AuthCredentialPatternView? = null
    private var lockPatternView: LockPatternView? = null
    private var biometricCallback: BiometricCallback? = null
    private var errorTimer: ErrorTimer? = null

    @After
    fun tearDown() {
        if (authContainer?.isAttachedToWindow == true) {
            ViewUtils.detachView(authContainer)
        }
    }

    @Test
    fun testAuthCredentialPatternView_onErrorTimeoutFinish_setPatternEnabled() {
        `when`(lockPatternUtils.getCredentialTypeForUser(anyInt()))
            .thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PATTERN)
        `when`(lockPatternUtils.getKeyguardStoredPasswordQuality(anyInt()))
            .thenReturn(PASSWORD_QUALITY_SOMETHING)
        val errorResponse: VerifyCredentialResponse = VerifyCredentialResponse.fromError()

        assertThat(initializeFingerprintContainer()).isNotNull()
        authContainer?.animateToCredentialUI()
        waitForIdleSync()

        authCredentialView = spy(authContainer?.mCredentialView as AuthCredentialPatternView)
        authCredentialView?.onCredentialVerified(errorResponse, 5000)
        errorTimer = authCredentialView?.mErrorTimer
        errorTimer?.onFinish()
        waitForIdleSync()

        verify(authCredentialView)?.onErrorTimeoutFinish()

        lockPatternView = authCredentialView?.mLockPatternView
        assertThat(lockPatternView?.isEnabled).isTrue()
    }

    private fun initializeFingerprintContainer(
        authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
        addToView: Boolean = true
    ) =
        initializeContainer(
            TestAuthContainerView(
                authenticators = authenticators,
                fingerprintProps = fingerprintSensorPropertiesInternal()
            ),
            addToView
        )

    private fun initializeContainer(
        view: TestAuthContainerView,
        addToView: Boolean
    ): TestAuthContainerView {
        authContainer = view
        if (addToView) {
            authContainer!!.addToView()
            biometricCallback = authContainer?.mBiometricCallback
        }
        return authContainer!!
    }

    private inner class TestAuthContainerView(
        authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
        fingerprintProps: List<FingerprintSensorPropertiesInternal> = listOf(),
        faceProps: List<FaceSensorPropertiesInternal> = listOf()
    ) :
        AuthContainerView(
            Config().apply {
                mContext = context
                mCallback = callback
                mSensorIds =
                    (fingerprintProps.map { it.sensorId } + faceProps.map { it.sensorId })
                        .toIntArray()
                mSkipAnimation = true
                mPromptInfo = PromptInfo().apply { this.authenticators = authenticators }
            },
            fingerprintProps,
            faceProps,
            wakefulnessLifecycle,
            userManager,
            lockPatternUtils,
            interactionJankMonitor,
            Handler(TestableLooper.get(this).looper),
            FakeExecutor(FakeSystemClock())
        ) {
        override fun postOnAnimation(runnable: Runnable) {
            runnable.run()
        }
    }

    override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages()

    private fun AuthContainerView.addToView() {
        ViewUtils.attachView(this)
        waitForIdleSync()
        assertThat(isAttachedToWindow).isTrue()
    }
}