Merge "Merge "Add coex tests." into tm-d1-dev am: 4f931d5197" into tm-d1-dev-plus-aosp
This commit is contained in:
committed by
Android (Google) Code Review
commit
5e36dfca62
@@ -1,26 +0,0 @@
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<com.android.systemui.biometrics.AuthBiometricView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/contents"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/auth_biometric_contents"/>
|
||||
|
||||
</com.android.systemui.biometrics.AuthBiometricView>
|
||||
@@ -59,7 +59,7 @@ import java.util.Set;
|
||||
/**
|
||||
* Contains the Biometric views (title, subtitle, icon, buttons, etc.) and its controllers.
|
||||
*/
|
||||
public class AuthBiometricView extends LinearLayout {
|
||||
public abstract class AuthBiometricView extends LinearLayout {
|
||||
|
||||
private static final String TAG = "AuthBiometricView";
|
||||
|
||||
@@ -116,7 +116,7 @@ public class AuthBiometricView extends LinearLayout {
|
||||
void onAction(int action);
|
||||
}
|
||||
|
||||
protected final Handler mHandler;
|
||||
private final Handler mHandler;
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
private final LockPatternUtils mLockPatternUtils;
|
||||
protected final int mTextColorError;
|
||||
@@ -155,8 +155,8 @@ public class AuthBiometricView extends LinearLayout {
|
||||
// Measurements when biometric view is showing text, buttons, etc.
|
||||
@Nullable @VisibleForTesting AuthDialog.LayoutParams mLayoutParams;
|
||||
|
||||
protected Callback mCallback;
|
||||
protected @BiometricState int mState;
|
||||
private Callback mCallback;
|
||||
@BiometricState private int mState;
|
||||
|
||||
private float mIconOriginalY;
|
||||
|
||||
@@ -250,18 +250,9 @@ public class AuthBiometricView extends LinearLayout {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the controller for managing the icons transitions during the prompt.
|
||||
*
|
||||
* Subclass should override.
|
||||
*/
|
||||
/** Create the controller for managing the icons transitions during the prompt.*/
|
||||
@NonNull
|
||||
protected AuthIconController createIconController() {
|
||||
return new AuthIconController(mContext, mIconView) {
|
||||
@Override
|
||||
public void updateIcon(int lastState, int newState) {}
|
||||
};
|
||||
}
|
||||
protected abstract AuthIconController createIconController();
|
||||
|
||||
void setPanelController(AuthPanelController panelController) {
|
||||
mPanelController = panelController;
|
||||
@@ -881,4 +872,14 @@ public class AuthBiometricView extends LinearLayout {
|
||||
@AuthDialog.DialogSize int getSize() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/** If authentication has successfully occurred and the view is done. */
|
||||
boolean isAuthenticated() {
|
||||
return mState == STATE_AUTHENTICATED;
|
||||
}
|
||||
|
||||
/** If authentication is currently in progress. */
|
||||
boolean isAuthenticating() {
|
||||
return mState == STATE_AUTHENTICATING;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
|
||||
import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.testing.TestableLooper.RunWithLooper
|
||||
import android.view.View
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@RunWithLooper(setAsMainLooper = true)
|
||||
@SmallTest
|
||||
class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
|
||||
|
||||
@JvmField @Rule
|
||||
var mockitoRule = MockitoJUnit.rule()
|
||||
|
||||
@Mock
|
||||
private lateinit var callback: AuthBiometricView.Callback
|
||||
@Mock
|
||||
private lateinit var panelController: AuthPanelController
|
||||
|
||||
private lateinit var biometricView: AuthBiometricFingerprintAndFaceView
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
biometricView = R.layout.auth_biometric_fingerprint_and_face_view
|
||||
.asTestAuthBiometricView(mContext, callback, panelController)
|
||||
waitForIdleSync()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
biometricView.destroyDialog()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fingerprintSuccessDoesNotRequireExplicitConfirmation() {
|
||||
biometricView.onDialogAnimatedIn()
|
||||
biometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT)
|
||||
waitForIdleSync()
|
||||
|
||||
assertThat(biometricView.isAuthenticated).isTrue()
|
||||
verify(callback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun faceSuccessRequiresExplicitConfirmation() {
|
||||
biometricView.onDialogAnimatedIn()
|
||||
biometricView.onAuthenticationSucceeded(TYPE_FACE)
|
||||
waitForIdleSync()
|
||||
|
||||
assertThat(biometricView.isAuthenticated).isFalse()
|
||||
assertThat(biometricView.isAuthenticating).isFalse()
|
||||
assertThat(biometricView.mConfirmButton.visibility).isEqualTo(View.GONE)
|
||||
verify(callback, never()).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED)
|
||||
|
||||
// icon acts as confirm button
|
||||
biometricView.mIconView.performClick()
|
||||
waitForIdleSync()
|
||||
|
||||
assertThat(biometricView.isAuthenticated).isTrue()
|
||||
verify(callback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ignoresFaceErrors() {
|
||||
biometricView.onDialogAnimatedIn()
|
||||
biometricView.onError(TYPE_FACE, "not a face")
|
||||
waitForIdleSync()
|
||||
|
||||
assertThat(biometricView.isAuthenticating).isTrue()
|
||||
verify(callback, never()).onAction(AuthBiometricView.Callback.ACTION_ERROR)
|
||||
|
||||
biometricView.onError(TYPE_FINGERPRINT, "that's a nope")
|
||||
waitForIdleSync()
|
||||
|
||||
verify(callback).onAction(AuthBiometricView.Callback.ACTION_ERROR)
|
||||
}
|
||||
|
||||
override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages()
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* 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.hardware.biometrics.BiometricAuthenticator
|
||||
import android.os.Bundle
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.testing.TestableLooper.RunWithLooper
|
||||
import android.view.View
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@RunWithLooper(setAsMainLooper = true)
|
||||
@SmallTest
|
||||
class AuthBiometricFingerprintViewTest : SysuiTestCase() {
|
||||
|
||||
@JvmField @Rule
|
||||
val mockitoRule = MockitoJUnit.rule()
|
||||
|
||||
@Mock
|
||||
private lateinit var callback: AuthBiometricView.Callback
|
||||
@Mock
|
||||
private lateinit var panelController: AuthPanelController
|
||||
|
||||
private lateinit var biometricView: AuthBiometricView
|
||||
|
||||
private fun createView(allowDeviceCredential: Boolean = false): AuthBiometricFingerprintView {
|
||||
val view = R.layout.auth_biometric_fingerprint_view.asTestAuthBiometricView(
|
||||
mContext, callback, panelController, allowDeviceCredential = allowDeviceCredential
|
||||
) as AuthBiometricFingerprintView
|
||||
waitForIdleSync()
|
||||
return view
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
biometricView = createView()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
biometricView.destroyDialog()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() {
|
||||
biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
|
||||
waitForIdleSync()
|
||||
|
||||
assertThat(biometricView.isAuthenticated).isTrue()
|
||||
verify(callback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() {
|
||||
biometricView.setRequireConfirmation(true)
|
||||
biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
|
||||
waitForIdleSync()
|
||||
|
||||
// TODO: this should be tested in the subclasses
|
||||
if (biometricView.supportsRequireConfirmation()) {
|
||||
verify(callback, never()).onAction(ArgumentMatchers.anyInt())
|
||||
assertThat(biometricView.mNegativeButton.visibility).isEqualTo(View.GONE)
|
||||
assertThat(biometricView.mCancelButton.visibility).isEqualTo(View.VISIBLE)
|
||||
assertThat(biometricView.mCancelButton.isEnabled).isTrue()
|
||||
assertThat(biometricView.mConfirmButton.isEnabled).isTrue()
|
||||
assertThat(biometricView.mIndicatorView.text)
|
||||
.isEqualTo(mContext.getText(R.string.biometric_dialog_tap_confirm))
|
||||
assertThat(biometricView.mIndicatorView.visibility).isEqualTo(View.VISIBLE)
|
||||
} else {
|
||||
assertThat(biometricView.isAuthenticated).isTrue()
|
||||
verify(callback).onAction(eq(AuthBiometricView.Callback.ACTION_AUTHENTICATED))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPositiveButton_sendsActionAuthenticated() {
|
||||
biometricView.mConfirmButton.performClick()
|
||||
waitForIdleSync()
|
||||
|
||||
verify(callback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED)
|
||||
assertThat(biometricView.isAuthenticated).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNegativeButton_beforeAuthentication_sendsActionButtonNegative() {
|
||||
biometricView.onDialogAnimatedIn()
|
||||
biometricView.mNegativeButton.performClick()
|
||||
waitForIdleSync()
|
||||
|
||||
verify(callback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCancelButton_whenPendingConfirmation_sendsActionUserCanceled() {
|
||||
biometricView.setRequireConfirmation(true)
|
||||
biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
|
||||
|
||||
assertThat(biometricView.mNegativeButton.visibility).isEqualTo(View.GONE)
|
||||
biometricView.mCancelButton.performClick()
|
||||
waitForIdleSync()
|
||||
|
||||
verify(callback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTryAgainButton_sendsActionTryAgain() {
|
||||
biometricView.mTryAgainButton.performClick()
|
||||
waitForIdleSync()
|
||||
|
||||
verify(callback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN)
|
||||
assertThat(biometricView.mTryAgainButton.visibility).isEqualTo(View.GONE)
|
||||
assertThat(biometricView.isAuthenticating).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnErrorSendsActionError() {
|
||||
biometricView.onError(BiometricAuthenticator.TYPE_FACE, "testError")
|
||||
waitForIdleSync()
|
||||
|
||||
verify(callback).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnErrorShowsMessage() {
|
||||
// prevent error state from instantly returning to authenticating in the test
|
||||
biometricView.mAnimationDurationHideDialog = 10_000
|
||||
|
||||
val message = "another error"
|
||||
biometricView.onError(BiometricAuthenticator.TYPE_FACE, message)
|
||||
waitForIdleSync()
|
||||
|
||||
assertThat(biometricView.isAuthenticating).isFalse()
|
||||
assertThat(biometricView.isAuthenticated).isFalse()
|
||||
assertThat(biometricView.mIndicatorView.visibility).isEqualTo(View.VISIBLE)
|
||||
assertThat(biometricView.mIndicatorView.text).isEqualTo(message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBackgroundClicked_sendsActionUserCanceled() {
|
||||
val view = View(mContext)
|
||||
biometricView.setBackgroundView(view)
|
||||
view.performClick()
|
||||
|
||||
verify(callback).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBackgroundClicked_afterAuthenticated_neverSendsUserCanceled() {
|
||||
val view = View(mContext)
|
||||
biometricView.setBackgroundView(view)
|
||||
biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
|
||||
view.performClick()
|
||||
|
||||
verify(callback, never())
|
||||
.onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBackgroundClicked_whenSmallDialog_neverSendsUserCanceled() {
|
||||
biometricView.mLayoutParams = AuthDialog.LayoutParams(0, 0)
|
||||
biometricView.updateSize(AuthDialog.SIZE_SMALL)
|
||||
val view = View(mContext)
|
||||
biometricView.setBackgroundView(view)
|
||||
view.performClick()
|
||||
|
||||
verify(callback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIgnoresUselessHelp() {
|
||||
biometricView.mAnimationDurationHideDialog = 10_000
|
||||
biometricView.onDialogAnimatedIn()
|
||||
waitForIdleSync()
|
||||
|
||||
assertThat(biometricView.isAuthenticating).isTrue()
|
||||
|
||||
val helpText = biometricView.mIndicatorView.text
|
||||
biometricView.onHelp(BiometricAuthenticator.TYPE_FINGERPRINT, "")
|
||||
waitForIdleSync()
|
||||
|
||||
// text should not change
|
||||
assertThat(biometricView.mIndicatorView.text).isEqualTo(helpText)
|
||||
verify(callback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRestoresState() {
|
||||
val requireConfirmation = true
|
||||
biometricView.mAnimationDurationHideDialog = 10_000
|
||||
val failureMessage = "testFailureMessage"
|
||||
biometricView.setRequireConfirmation(requireConfirmation)
|
||||
biometricView.onAuthenticationFailed(BiometricAuthenticator.TYPE_FACE, failureMessage)
|
||||
waitForIdleSync()
|
||||
|
||||
val state = Bundle()
|
||||
biometricView.onSaveState(state)
|
||||
assertThat(biometricView.mTryAgainButton.visibility).isEqualTo(View.GONE)
|
||||
assertThat(state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY))
|
||||
.isEqualTo(View.GONE)
|
||||
assertThat(state.getInt(AuthDialog.KEY_BIOMETRIC_STATE))
|
||||
.isEqualTo(AuthBiometricView.STATE_ERROR)
|
||||
assertThat(biometricView.mIndicatorView.visibility).isEqualTo(View.VISIBLE)
|
||||
assertThat(state.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING)).isTrue()
|
||||
assertThat(biometricView.mIndicatorView.text).isEqualTo(failureMessage)
|
||||
assertThat(state.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING))
|
||||
.isEqualTo(failureMessage)
|
||||
|
||||
// TODO: Test dialog size. Should move requireConfirmation to buildBiometricPromptBundle
|
||||
|
||||
// Create new dialog and restore the previous state into it
|
||||
biometricView.destroyDialog()
|
||||
biometricView = createView()
|
||||
biometricView.restoreState(state)
|
||||
biometricView.mAnimationDurationHideDialog = 10_000
|
||||
biometricView.setRequireConfirmation(requireConfirmation)
|
||||
waitForIdleSync()
|
||||
|
||||
assertThat(biometricView.mTryAgainButton.visibility).isEqualTo(View.GONE)
|
||||
assertThat(biometricView.mIndicatorView.visibility).isEqualTo(View.VISIBLE)
|
||||
|
||||
// TODO: Test restored text. Currently cannot test this, since it gets restored only after
|
||||
// dialog size is known.
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCredentialButton_whenDeviceCredentialAllowed() {
|
||||
biometricView.destroyDialog()
|
||||
biometricView = createView(allowDeviceCredential = true)
|
||||
|
||||
assertThat(biometricView.mUseCredentialButton.visibility).isEqualTo(View.VISIBLE)
|
||||
assertThat(biometricView.mNegativeButton.visibility).isEqualTo(View.GONE)
|
||||
|
||||
biometricView.mUseCredentialButton.performClick()
|
||||
waitForIdleSync()
|
||||
|
||||
verify(callback).onAction(AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL)
|
||||
}
|
||||
|
||||
override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages()
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
|
||||
import static android.hardware.biometrics.BiometricManager.Authenticators;
|
||||
|
||||
import static com.android.systemui.biometrics.AuthBiometricView.Callback.ACTION_AUTHENTICATED;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.hardware.biometrics.PromptInfo;
|
||||
import android.os.Bundle;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.testing.TestableLooper.RunWithLooper;
|
||||
import android.testing.ViewUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@RunWithLooper
|
||||
@SmallTest
|
||||
public class AuthBiometricViewTest extends SysuiTestCase {
|
||||
|
||||
@Rule
|
||||
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private AuthBiometricView.Callback mCallback;
|
||||
@Mock
|
||||
private AuthPanelController mPanelController;
|
||||
|
||||
private AuthBiometricView mBiometricView;
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
destroyDialog();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() {
|
||||
initDialog(false /* allowDeviceCredential */, mCallback);
|
||||
|
||||
// The onAuthenticated runnable is posted when authentication succeeds.
|
||||
mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
|
||||
waitForIdleSync();
|
||||
assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
|
||||
verify(mCallback).onAction(ACTION_AUTHENTICATED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() {
|
||||
initDialog(false /* allowDeviceCredential */, mCallback);
|
||||
|
||||
mBiometricView.setRequireConfirmation(true);
|
||||
mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
|
||||
waitForIdleSync();
|
||||
|
||||
// TODO: this should be tested in the subclasses
|
||||
if (mBiometricView.supportsRequireConfirmation()) {
|
||||
assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState);
|
||||
|
||||
verify(mCallback, never()).onAction(anyInt());
|
||||
|
||||
assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility());
|
||||
assertEquals(View.VISIBLE, mBiometricView.mCancelButton.getVisibility());
|
||||
assertTrue(mBiometricView.mCancelButton.isEnabled());
|
||||
|
||||
assertTrue(mBiometricView.mConfirmButton.isEnabled());
|
||||
assertEquals(mContext.getText(R.string.biometric_dialog_tap_confirm),
|
||||
mBiometricView.mIndicatorView.getText());
|
||||
assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
|
||||
} else {
|
||||
assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
|
||||
verify(mCallback).onAction(eq(ACTION_AUTHENTICATED));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPositiveButton_sendsActionAuthenticated() {
|
||||
initDialog(false /* allowDeviceCredential */, mCallback);
|
||||
|
||||
mBiometricView.mConfirmButton.performClick();
|
||||
waitForIdleSync();
|
||||
|
||||
verify(mCallback).onAction(ACTION_AUTHENTICATED);
|
||||
assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegativeButton_beforeAuthentication_sendsActionButtonNegative() {
|
||||
initDialog(false /* allowDeviceCredential */, mCallback);
|
||||
|
||||
mBiometricView.onDialogAnimatedIn();
|
||||
mBiometricView.mNegativeButton.performClick();
|
||||
waitForIdleSync();
|
||||
|
||||
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelButton_whenPendingConfirmation_sendsActionUserCanceled() {
|
||||
initDialog(false /* allowDeviceCredential */, mCallback);
|
||||
|
||||
mBiometricView.setRequireConfirmation(true);
|
||||
mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
|
||||
|
||||
assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility());
|
||||
|
||||
mBiometricView.mCancelButton.performClick();
|
||||
waitForIdleSync();
|
||||
|
||||
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTryAgainButton_sendsActionTryAgain() {
|
||||
initDialog(false /* allowDeviceCredential */, mCallback);
|
||||
|
||||
mBiometricView.mTryAgainButton.performClick();
|
||||
waitForIdleSync();
|
||||
|
||||
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
|
||||
assertEquals(AuthBiometricView.STATE_AUTHENTICATING, mBiometricView.mState);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("flaky, b/189031816")
|
||||
public void testError_sendsActionError() {
|
||||
initDialog(false /* allowDeviceCredential */, mCallback);
|
||||
final String testError = "testError";
|
||||
mBiometricView.onError(TYPE_FACE, testError);
|
||||
waitForIdleSync();
|
||||
|
||||
verify(mCallback).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR));
|
||||
assertEquals(AuthBiometricView.STATE_IDLE, mBiometricView.mState);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackgroundClicked_sendsActionUserCanceled() {
|
||||
initDialog(false /* allowDeviceCredential */, mCallback);
|
||||
|
||||
View view = new View(mContext);
|
||||
mBiometricView.setBackgroundView(view);
|
||||
view.performClick();
|
||||
verify(mCallback).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackgroundClicked_afterAuthenticated_neverSendsUserCanceled() {
|
||||
initDialog(false /* allowDeviceCredential */, mCallback);
|
||||
|
||||
View view = new View(mContext);
|
||||
mBiometricView.setBackgroundView(view);
|
||||
mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT);
|
||||
view.performClick();
|
||||
verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackgroundClicked_whenSmallDialog_neverSendsUserCanceled() {
|
||||
initDialog(false /* allowDeviceCredential */, mCallback);
|
||||
mBiometricView.mLayoutParams = new AuthDialog.LayoutParams(0, 0);
|
||||
mBiometricView.updateSize(AuthDialog.SIZE_SMALL);
|
||||
|
||||
View view = new View(mContext);
|
||||
mBiometricView.setBackgroundView(view);
|
||||
view.performClick();
|
||||
verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoresUselessHelp() {
|
||||
initDialog(false /* allowDeviceCredential */, mCallback);
|
||||
|
||||
mBiometricView.onDialogAnimatedIn();
|
||||
waitForIdleSync();
|
||||
|
||||
assertEquals(AuthBiometricView.STATE_AUTHENTICATING, mBiometricView.mState);
|
||||
|
||||
mBiometricView.onHelp(TYPE_FINGERPRINT, "");
|
||||
waitForIdleSync();
|
||||
|
||||
assertEquals("", mBiometricView.mIndicatorView.getText());
|
||||
verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR));
|
||||
assertEquals(AuthBiometricView.STATE_AUTHENTICATING, mBiometricView.mState);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestoresState() {
|
||||
final boolean requireConfirmation = true;
|
||||
|
||||
initDialog(false /* allowDeviceCredential */, mCallback, null, 10000);
|
||||
|
||||
final String failureMessage = "testFailureMessage";
|
||||
mBiometricView.setRequireConfirmation(requireConfirmation);
|
||||
mBiometricView.onAuthenticationFailed(TYPE_FACE, failureMessage);
|
||||
waitForIdleSync();
|
||||
|
||||
Bundle state = new Bundle();
|
||||
mBiometricView.onSaveState(state);
|
||||
|
||||
assertEquals(View.GONE, mBiometricView.mTryAgainButton.getVisibility());
|
||||
assertEquals(View.GONE, state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
|
||||
|
||||
assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
|
||||
assertEquals(AuthBiometricView.STATE_ERROR, state.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
|
||||
|
||||
assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
|
||||
assertTrue(state.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING));
|
||||
|
||||
assertEquals(failureMessage, mBiometricView.mIndicatorView.getText());
|
||||
assertEquals(failureMessage, state.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING));
|
||||
|
||||
// TODO: Test dialog size. Should move requireConfirmation to buildBiometricPromptBundle
|
||||
|
||||
// Create new dialog and restore the previous state into it
|
||||
destroyDialog();
|
||||
initDialog(false /* allowDeviceCredential */, mCallback, state, 10000);
|
||||
mBiometricView.mAnimationDurationHideDialog = 10000;
|
||||
mBiometricView.setRequireConfirmation(requireConfirmation);
|
||||
waitForIdleSync();
|
||||
|
||||
assertEquals(View.GONE, mBiometricView.mTryAgainButton.getVisibility());
|
||||
assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
|
||||
assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
|
||||
|
||||
// TODO: Test restored text. Currently cannot test this, since it gets restored only after
|
||||
// dialog size is known.
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCredentialButton_whenDeviceCredentialAllowed() throws InterruptedException {
|
||||
initDialog(true /* allowDeviceCredential */, mCallback);
|
||||
|
||||
assertEquals(View.VISIBLE, mBiometricView.mUseCredentialButton.getVisibility());
|
||||
assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility());
|
||||
mBiometricView.mUseCredentialButton.performClick();
|
||||
waitForIdleSync();
|
||||
|
||||
verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL);
|
||||
}
|
||||
|
||||
private PromptInfo buildPromptInfo(boolean allowDeviceCredential) {
|
||||
PromptInfo promptInfo = new PromptInfo();
|
||||
promptInfo.setTitle("Title");
|
||||
int authenticators = Authenticators.BIOMETRIC_WEAK;
|
||||
if (allowDeviceCredential) {
|
||||
authenticators |= Authenticators.DEVICE_CREDENTIAL;
|
||||
} else {
|
||||
promptInfo.setNegativeButtonText("Negative");
|
||||
}
|
||||
promptInfo.setAuthenticators(authenticators);
|
||||
return promptInfo;
|
||||
}
|
||||
|
||||
private void initDialog(boolean allowDeviceCredential, AuthBiometricView.Callback callback) {
|
||||
initDialog(allowDeviceCredential, callback,
|
||||
null /* savedState */, 0 /* hideDelay */);
|
||||
}
|
||||
|
||||
private void initDialog(boolean allowDeviceCredential,
|
||||
AuthBiometricView.Callback callback, Bundle savedState, int hideDelay) {
|
||||
final LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
mBiometricView = (AuthBiometricView) inflater.inflate(
|
||||
R.layout.auth_biometric_view, null, false);
|
||||
mBiometricView.mAnimationDurationLong = 0;
|
||||
mBiometricView.mAnimationDurationShort = 0;
|
||||
mBiometricView.mAnimationDurationHideDialog = hideDelay;
|
||||
mBiometricView.setPromptInfo(buildPromptInfo(allowDeviceCredential));
|
||||
mBiometricView.setCallback(callback);
|
||||
mBiometricView.restoreState(savedState);
|
||||
ViewUtils.attachView(mBiometricView);
|
||||
mBiometricView.setPanelController(mPanelController);
|
||||
waitForIdleSync();
|
||||
}
|
||||
|
||||
private void destroyDialog() {
|
||||
if (mBiometricView != null && mBiometricView.isAttachedToWindow()) {
|
||||
ViewUtils.detachView(mBiometricView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void waitForIdleSync() {
|
||||
TestableLooper.get(this).processAllMessages();
|
||||
super.waitForIdleSync();
|
||||
}
|
||||
}
|
||||
@@ -16,13 +16,11 @@
|
||||
package com.android.systemui.biometrics
|
||||
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.hardware.biometrics.BiometricAuthenticator
|
||||
import android.hardware.biometrics.BiometricConstants
|
||||
import android.hardware.biometrics.BiometricManager
|
||||
import android.hardware.biometrics.ComponentInfoInternal
|
||||
import android.hardware.biometrics.PromptInfo
|
||||
import android.hardware.biometrics.SensorProperties
|
||||
import android.hardware.face.FaceSensorPropertiesInternal
|
||||
import android.hardware.fingerprint.FingerprintSensorProperties
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
@@ -40,7 +38,6 @@ import com.android.internal.widget.LockPatternUtils
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.keyguard.WakefulnessLifecycle
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
@@ -57,12 +54,12 @@ import org.mockito.junit.MockitoJUnit
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@RunWithLooper
|
||||
@RunWithLooper(setAsMainLooper = true)
|
||||
@SmallTest
|
||||
class AuthContainerViewTest : SysuiTestCase() {
|
||||
|
||||
@JvmField @Rule
|
||||
var rule = MockitoJUnit.rule()
|
||||
var mockitoRule = MockitoJUnit.rule()
|
||||
|
||||
@Mock
|
||||
lateinit var callback: AuthDialogCallback
|
||||
@@ -86,13 +83,13 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testNotifiesAnimatedIn() {
|
||||
initializeContainer()
|
||||
initializeFingerprintContainer()
|
||||
verify(callback).onDialogAnimatedIn()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIgnoresAnimatedInWhenDismissed() {
|
||||
val container = initializeContainer(addToView = false)
|
||||
val container = initializeFingerprintContainer(addToView = false)
|
||||
container.dismissFromSystemServer()
|
||||
waitForIdleSync()
|
||||
|
||||
@@ -107,7 +104,7 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testActionAuthenticated_sendsDismissedAuthenticated() {
|
||||
val container = initializeContainer()
|
||||
val container = initializeFingerprintContainer()
|
||||
container.mBiometricCallback.onAction(
|
||||
AuthBiometricView.Callback.ACTION_AUTHENTICATED
|
||||
)
|
||||
@@ -122,7 +119,7 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testActionUserCanceled_sendsDismissedUserCanceled() {
|
||||
val container = initializeContainer()
|
||||
val container = initializeFingerprintContainer()
|
||||
container.mBiometricCallback.onAction(
|
||||
AuthBiometricView.Callback.ACTION_USER_CANCELED
|
||||
)
|
||||
@@ -140,7 +137,7 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testActionButtonNegative_sendsDismissedButtonNegative() {
|
||||
val container = initializeContainer()
|
||||
val container = initializeFingerprintContainer()
|
||||
container.mBiometricCallback.onAction(
|
||||
AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE
|
||||
)
|
||||
@@ -155,7 +152,9 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testActionTryAgain_sendsTryAgain() {
|
||||
val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
|
||||
val container = initializeFingerprintContainer(
|
||||
authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||
)
|
||||
container.mBiometricCallback.onAction(
|
||||
AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN
|
||||
)
|
||||
@@ -166,8 +165,8 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testActionError_sendsDismissedError() {
|
||||
val container = initializeContainer()
|
||||
authContainer!!.mBiometricCallback.onAction(
|
||||
val container = initializeFingerprintContainer()
|
||||
container.mBiometricCallback.onAction(
|
||||
AuthBiometricView.Callback.ACTION_ERROR
|
||||
)
|
||||
waitForIdleSync()
|
||||
@@ -181,8 +180,8 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() {
|
||||
val container = initializeContainer(
|
||||
BiometricManager.Authenticators.BIOMETRIC_WEAK or
|
||||
val container = initializeFingerprintContainer(
|
||||
authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or
|
||||
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
)
|
||||
container.mBiometricCallback.onAction(
|
||||
@@ -196,8 +195,8 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() {
|
||||
val container = initializeContainer(
|
||||
BiometricManager.Authenticators.BIOMETRIC_WEAK or
|
||||
val container = initializeFingerprintContainer(
|
||||
authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or
|
||||
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
)
|
||||
container.animateToCredentialUI()
|
||||
@@ -208,7 +207,7 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testShowBiometricUI() {
|
||||
val container = initializeContainer()
|
||||
val container = initializeFingerprintContainer()
|
||||
|
||||
waitForIdleSync()
|
||||
|
||||
@@ -218,7 +217,9 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testShowCredentialUI() {
|
||||
val container = initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
|
||||
val container = initializeFingerprintContainer(
|
||||
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
)
|
||||
waitForIdleSync()
|
||||
|
||||
assertThat(container.hasCredentialView()).isTrue()
|
||||
@@ -232,7 +233,9 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
|
||||
)
|
||||
|
||||
val container = initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
|
||||
val container = initializeFingerprintContainer(
|
||||
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
)
|
||||
waitForIdleSync()
|
||||
|
||||
assertThat(container.hasCredentialPatternView()).isTrue()
|
||||
@@ -249,7 +252,9 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
// In the credential view, clicking on the background (to cancel authentication) is not
|
||||
// valid. Thus, the listener should be null, and it should not be in the accessibility
|
||||
// hierarchy.
|
||||
val container = initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL)
|
||||
val container = initializeFingerprintContainer(
|
||||
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
)
|
||||
waitForIdleSync()
|
||||
|
||||
assertThat(container.hasCredentialPasswordView()).isTrue()
|
||||
@@ -277,49 +282,52 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.ime()) == 0).isTrue()
|
||||
}
|
||||
|
||||
private fun initializeContainer(
|
||||
@Test
|
||||
fun coexFaceRestartsOnTouch() {
|
||||
val container = initializeCoexContainer()
|
||||
|
||||
container.onPointerDown()
|
||||
waitForIdleSync()
|
||||
|
||||
container.onAuthenticationFailed(BiometricAuthenticator.TYPE_FACE, "failed")
|
||||
waitForIdleSync()
|
||||
|
||||
verify(callback, never()).onTryAgainPressed()
|
||||
|
||||
container.onPointerDown()
|
||||
waitForIdleSync()
|
||||
|
||||
verify(callback).onTryAgainPressed()
|
||||
}
|
||||
|
||||
private fun initializeFingerprintContainer(
|
||||
authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
|
||||
addToView: Boolean = true
|
||||
) = initializeContainer(
|
||||
TestAuthContainerView(
|
||||
authenticators = authenticators,
|
||||
fingerprintProps = fingerprintSensorPropertiesInternal()
|
||||
),
|
||||
addToView
|
||||
)
|
||||
|
||||
private fun initializeCoexContainer(
|
||||
authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
|
||||
addToView: Boolean = true
|
||||
) = initializeContainer(
|
||||
TestAuthContainerView(
|
||||
authenticators = authenticators,
|
||||
fingerprintProps = fingerprintSensorPropertiesInternal(),
|
||||
faceProps = faceSensorPropertiesInternal()
|
||||
),
|
||||
addToView
|
||||
)
|
||||
|
||||
private fun initializeContainer(
|
||||
view: TestAuthContainerView,
|
||||
addToView: Boolean
|
||||
): TestAuthContainerView {
|
||||
val config = AuthContainerView.Config()
|
||||
config.mContext = mContext
|
||||
config.mCallback = callback
|
||||
config.mSensorIds = intArrayOf(0)
|
||||
config.mSkipAnimation = true
|
||||
config.mPromptInfo = PromptInfo()
|
||||
config.mPromptInfo.authenticators = authenticators
|
||||
val componentInfo = listOf(
|
||||
ComponentInfoInternal(
|
||||
"faceSensor" /* componentId */,
|
||||
"vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
|
||||
"00000001" /* serialNumber */, "" /* softwareVersion */
|
||||
),
|
||||
ComponentInfoInternal(
|
||||
"matchingAlgorithm" /* componentId */,
|
||||
"" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
|
||||
"vendor/version/revision" /* softwareVersion */
|
||||
)
|
||||
)
|
||||
val fpProps = listOf(
|
||||
FingerprintSensorPropertiesInternal(
|
||||
0,
|
||||
SensorProperties.STRENGTH_STRONG,
|
||||
5 /* maxEnrollmentsPerUser */,
|
||||
componentInfo,
|
||||
FingerprintSensorProperties.TYPE_REAR,
|
||||
false /* resetLockoutRequiresHardwareAuthToken */
|
||||
)
|
||||
)
|
||||
authContainer = TestAuthContainerView(
|
||||
config,
|
||||
fpProps,
|
||||
listOf(),
|
||||
wakefulnessLifecycle,
|
||||
userManager,
|
||||
lockPatternUtils,
|
||||
Handler(TestableLooper.get(this).looper),
|
||||
FakeExecutor(FakeSystemClock())
|
||||
)
|
||||
authContainer = view
|
||||
|
||||
if (addToView) {
|
||||
authContainer!!.addToView()
|
||||
@@ -329,27 +337,34 @@ class AuthContainerViewTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
private inner class TestAuthContainerView(
|
||||
config: Config,
|
||||
fpProps: List<FingerprintSensorPropertiesInternal>,
|
||||
faceProps: List<FaceSensorPropertiesInternal>,
|
||||
wakefulnessLifecycle: WakefulnessLifecycle,
|
||||
userManager: UserManager,
|
||||
lockPatternUtils: LockPatternUtils,
|
||||
mainHandler: Handler,
|
||||
bgExecutor: DelayableExecutor
|
||||
authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
|
||||
fingerprintProps: List<FingerprintSensorPropertiesInternal> = listOf(),
|
||||
faceProps: List<FaceSensorPropertiesInternal> = listOf()
|
||||
) : AuthContainerView(
|
||||
config, fpProps, faceProps,
|
||||
wakefulnessLifecycle, userManager, lockPatternUtils, mainHandler, bgExecutor
|
||||
Config().apply {
|
||||
mContext = this@AuthContainerViewTest.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,
|
||||
Handler(TestableLooper.get(this).looper),
|
||||
FakeExecutor(FakeSystemClock())
|
||||
) {
|
||||
override fun postOnAnimation(runnable: Runnable) {
|
||||
runnable.run()
|
||||
}
|
||||
}
|
||||
|
||||
override fun waitForIdleSync() {
|
||||
TestableLooper.get(this).processAllMessages()
|
||||
super.waitForIdleSync()
|
||||
}
|
||||
override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages()
|
||||
|
||||
private fun AuthContainerView.addToView() {
|
||||
ViewUtils.attachView(this)
|
||||
|
||||
@@ -16,27 +16,123 @@
|
||||
|
||||
package com.android.systemui.biometrics
|
||||
|
||||
import android.annotation.IdRes
|
||||
import android.content.Context
|
||||
import android.hardware.biometrics.BiometricManager
|
||||
import android.hardware.biometrics.ComponentInfoInternal
|
||||
import android.hardware.biometrics.SensorLocationInternal
|
||||
import android.hardware.biometrics.PromptInfo
|
||||
import android.hardware.biometrics.SensorProperties
|
||||
import android.hardware.face.FaceSensorPropertiesInternal
|
||||
import android.hardware.face.FaceSensorProperties
|
||||
import android.hardware.fingerprint.FingerprintSensorProperties
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
|
||||
import android.os.Bundle
|
||||
|
||||
/** Creates properties from the sensor location with test values. */
|
||||
fun SensorLocationInternal.asFingerprintSensorProperties(
|
||||
sensorId: Int = 22,
|
||||
@SensorProperties.Strength sensorStrength: Int = SensorProperties.STRENGTH_WEAK,
|
||||
@FingerprintSensorProperties.SensorType sensorType: Int =
|
||||
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
|
||||
maxEnrollmentsPerUser: Int = 1,
|
||||
info: List<ComponentInfoInternal> = listOf(ComponentInfoInternal("a", "b", "c", "d", "e")),
|
||||
resetLockoutRequiresHardwareAuthToken: Boolean = false
|
||||
) = FingerprintSensorPropertiesInternal(
|
||||
sensorId,
|
||||
sensorStrength,
|
||||
maxEnrollmentsPerUser,
|
||||
info,
|
||||
sensorType,
|
||||
resetLockoutRequiresHardwareAuthToken,
|
||||
listOf(this)
|
||||
)
|
||||
import android.testing.ViewUtils
|
||||
import android.view.LayoutInflater
|
||||
|
||||
/**
|
||||
* Inflate the given BiometricPrompt layout and initialize it with test parameters.
|
||||
*
|
||||
* This attaches the view so be sure to call [destroyDialog] at the end of the test.
|
||||
*/
|
||||
@IdRes
|
||||
internal fun <T : AuthBiometricView> Int.asTestAuthBiometricView(
|
||||
context: Context,
|
||||
callback: AuthBiometricView.Callback,
|
||||
panelController: AuthPanelController,
|
||||
allowDeviceCredential: Boolean = false,
|
||||
savedState: Bundle? = null,
|
||||
hideDelay: Int = 0
|
||||
): T {
|
||||
val view = LayoutInflater.from(context).inflate(this, null, false) as T
|
||||
view.mAnimationDurationLong = 0
|
||||
view.mAnimationDurationShort = 0
|
||||
view.mAnimationDurationHideDialog = hideDelay
|
||||
view.setPromptInfo(buildPromptInfo(allowDeviceCredential))
|
||||
view.setCallback(callback)
|
||||
view.restoreState(savedState)
|
||||
view.setPanelController(panelController)
|
||||
|
||||
ViewUtils.attachView(view)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun buildPromptInfo(allowDeviceCredential: Boolean): PromptInfo {
|
||||
val promptInfo = PromptInfo()
|
||||
promptInfo.title = "Title"
|
||||
var authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||
if (allowDeviceCredential) {
|
||||
authenticators = authenticators or BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
} else {
|
||||
promptInfo.negativeButtonText = "Negative"
|
||||
}
|
||||
promptInfo.authenticators = authenticators
|
||||
return promptInfo
|
||||
}
|
||||
|
||||
/** Detach the view, if needed. */
|
||||
internal fun AuthBiometricView?.destroyDialog() {
|
||||
if (this != null && isAttachedToWindow) {
|
||||
ViewUtils.detachView(this)
|
||||
}
|
||||
}
|
||||
|
||||
/** Create [FingerprintSensorPropertiesInternal] for a test. */
|
||||
internal fun fingerprintSensorPropertiesInternal(
|
||||
ids: List<Int> = listOf(0)
|
||||
): List<FingerprintSensorPropertiesInternal> {
|
||||
val componentInfo = listOf(
|
||||
ComponentInfoInternal(
|
||||
"fingerprintSensor" /* componentId */,
|
||||
"vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
|
||||
"00000001" /* serialNumber */, "" /* softwareVersion */
|
||||
),
|
||||
ComponentInfoInternal(
|
||||
"matchingAlgorithm" /* componentId */,
|
||||
"" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
|
||||
"vendor/version/revision" /* softwareVersion */
|
||||
)
|
||||
)
|
||||
return ids.map { id ->
|
||||
FingerprintSensorPropertiesInternal(
|
||||
id,
|
||||
SensorProperties.STRENGTH_STRONG,
|
||||
5 /* maxEnrollmentsPerUser */,
|
||||
componentInfo,
|
||||
FingerprintSensorProperties.TYPE_REAR,
|
||||
false /* resetLockoutRequiresHardwareAuthToken */
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Create [FaceSensorPropertiesInternal] for a test. */
|
||||
internal fun faceSensorPropertiesInternal(
|
||||
ids: List<Int> = listOf(1)
|
||||
): List<FaceSensorPropertiesInternal> {
|
||||
val componentInfo = listOf(
|
||||
ComponentInfoInternal(
|
||||
"faceSensor" /* componentId */,
|
||||
"vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
|
||||
"00000001" /* serialNumber */, "" /* softwareVersion */
|
||||
),
|
||||
ComponentInfoInternal(
|
||||
"matchingAlgorithm" /* componentId */,
|
||||
"" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
|
||||
"vendor/version/revision" /* softwareVersion */
|
||||
)
|
||||
)
|
||||
return ids.map { id ->
|
||||
FaceSensorPropertiesInternal(
|
||||
id,
|
||||
SensorProperties.STRENGTH_STRONG,
|
||||
2 /* maxEnrollmentsPerUser */,
|
||||
componentInfo,
|
||||
FaceSensorProperties.TYPE_RGB,
|
||||
true /* supportsFaceDetection */,
|
||||
true /* supportsSelfIllumination */,
|
||||
false /* resetLockoutRequiresHardwareAuthToken */
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user