[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
This commit is contained in:
@@ -120,7 +120,7 @@ public class AuthContainerView extends LinearLayout
|
|||||||
@VisibleForTesting final BiometricCallback mBiometricCallback;
|
@VisibleForTesting final BiometricCallback mBiometricCallback;
|
||||||
|
|
||||||
@Nullable private AuthBiometricView mBiometricView;
|
@Nullable private AuthBiometricView mBiometricView;
|
||||||
@Nullable private AuthCredentialView mCredentialView;
|
@VisibleForTesting @Nullable AuthCredentialView mCredentialView;
|
||||||
private final AuthPanelController mPanelController;
|
private final AuthPanelController mPanelController;
|
||||||
private final FrameLayout mFrameLayout;
|
private final FrameLayout mFrameLayout;
|
||||||
private final ImageView mBackgroundView;
|
private final ImageView mBackgroundView;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.widget.LockPatternChecker;
|
import com.android.internal.widget.LockPatternChecker;
|
||||||
import com.android.internal.widget.LockPatternUtils;
|
import com.android.internal.widget.LockPatternUtils;
|
||||||
import com.android.internal.widget.LockPatternView;
|
import com.android.internal.widget.LockPatternView;
|
||||||
@@ -34,7 +35,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class AuthCredentialPatternView extends AuthCredentialView {
|
public class AuthCredentialPatternView extends AuthCredentialView {
|
||||||
|
|
||||||
private LockPatternView mLockPatternView;
|
@VisibleForTesting LockPatternView mLockPatternView;
|
||||||
|
|
||||||
private class UnlockPatternListener implements LockPatternView.OnPatternListener {
|
private class UnlockPatternListener implements LockPatternView.OnPatternListener {
|
||||||
|
|
||||||
@@ -93,9 +94,7 @@ public class AuthCredentialPatternView extends AuthCredentialView {
|
|||||||
@Override
|
@Override
|
||||||
protected void onErrorTimeoutFinish() {
|
protected void onErrorTimeoutFinish() {
|
||||||
super.onErrorTimeoutFinish();
|
super.onErrorTimeoutFinish();
|
||||||
// select to enable marquee unless a screen reader is enabled
|
mLockPatternView.setEnabled(true);
|
||||||
mLockPatternView.setEnabled(!mAccessibilityManager.isEnabled()
|
|
||||||
|| !mAccessibilityManager.isTouchExplorationEnabled());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthCredentialPatternView(Context context, AttributeSet attrs) {
|
public AuthCredentialPatternView(Context context, AttributeSet attrs) {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import com.android.internal.widget.LockPatternUtils;
|
import com.android.internal.widget.LockPatternUtils;
|
||||||
import com.android.internal.widget.VerifyCredentialResponse;
|
import com.android.internal.widget.VerifyCredentialResponse;
|
||||||
@@ -98,7 +99,7 @@ public abstract class AuthCredentialView extends LinearLayout {
|
|||||||
protected int mUserId;
|
protected int mUserId;
|
||||||
protected long mOperationId;
|
protected long mOperationId;
|
||||||
protected int mEffectiveUserId;
|
protected int mEffectiveUserId;
|
||||||
protected ErrorTimer mErrorTimer;
|
@VisibleForTesting ErrorTimer mErrorTimer;
|
||||||
|
|
||||||
protected @Background DelayableExecutor mBackgroundExecutor;
|
protected @Background DelayableExecutor mBackgroundExecutor;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user