4/n: Make BiometricDialogView testable

Fixes: 138628043

Test: Manual BiometricPrompt test
Test: atest BiometricDialogViewTest

Change-Id: I105f44aad5ffe5f86a68ae688f7b56a5dd42c37d
This commit is contained in:
Kevin Chyn
2019-08-09 17:35:41 -07:00
parent 050315f61a
commit 27fafb7655
4 changed files with 249 additions and 22 deletions

View File

@@ -47,6 +47,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -82,7 +83,8 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
protected static final int STATE_PENDING_CONFIRMATION = 3;
protected static final int STATE_AUTHENTICATED = 4;
private final WakefulnessLifecycle mWakefulnessLifecycle;
@VisibleForTesting
final WakefulnessLifecycle mWakefulnessLifecycle;
private final AccessibilityManager mAccessibilityManager;
private final IBinder mWindowToken = new Binder();
private final Interpolator mLinearOutSlowIn;
@@ -96,14 +98,22 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
protected final ViewGroup mLayout;
protected final LinearLayout mDialog;
protected final TextView mTitleText;
protected final TextView mSubtitleText;
protected final TextView mDescriptionText;
protected final ImageView mBiometricIcon;
protected final TextView mErrorText;
protected final Button mPositiveButton;
protected final Button mNegativeButton;
protected final Button mTryAgainButton;
@VisibleForTesting
final TextView mTitleText;
@VisibleForTesting
final TextView mSubtitleText;
@VisibleForTesting
final TextView mDescriptionText;
@VisibleForTesting
final ImageView mBiometricIcon;
@VisibleForTesting
final TextView mErrorText;
@VisibleForTesting
final Button mPositiveButton;
@VisibleForTesting
final Button mNegativeButton;
@VisibleForTesting
final Button mTryAgainButton;
protected final int mTextColor;
@@ -147,7 +157,8 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
}
};
private final WakefulnessLifecycle.Observer mWakefulnessObserver =
@VisibleForTesting
final WakefulnessLifecycle.Observer mWakefulnessObserver =
new WakefulnessLifecycle.Observer() {
@Override
public void onStartedGoingToSleep() {
@@ -213,11 +224,15 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
}
public BiometricDialogView build(int type) {
return build(type, new Injector());
}
public BiometricDialogView build(int type, Injector injector) {
BiometricDialogView dialog;
if (type == TYPE_FINGERPRINT) {
dialog = new FingerprintDialogView(mContext, mCallback);
dialog = new FingerprintDialogView(mContext, mCallback, injector);
} else if (type == TYPE_FACE) {
dialog = new FaceDialogView(mContext, mCallback);
dialog = new FaceDialogView(mContext, mCallback, injector);
} else {
return null;
}
@@ -229,9 +244,15 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
}
}
protected BiometricDialogView(Context context, DialogViewCallback callback) {
public static class Injector {
public WakefulnessLifecycle getWakefulnessLifecycle() {
return Dependency.get(WakefulnessLifecycle.class);
}
}
protected BiometricDialogView(Context context, DialogViewCallback callback, Injector injector) {
super(context);
mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
mWakefulnessLifecycle = injector.getWakefulnessLifecycle();
mCallback = callback;
mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;

View File

@@ -33,6 +33,7 @@ import android.util.Log;
import android.view.View;
import android.view.ViewOutlineProvider;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.biometrics.DialogViewCallback;
@@ -53,7 +54,8 @@ public class FaceDialogView extends BiometricDialogView {
private static final int TEXT_ANIMATE_DISTANCE = 32; // dp
private static final int SIZE_UNKNOWN = 0;
private static final int SIZE_SMALL = 1;
@VisibleForTesting
static final int SIZE_SMALL = 1;
private static final int SIZE_GROWING = 2;
private static final int SIZE_BIG = 3;
@@ -153,13 +155,13 @@ public class FaceDialogView extends BiometricDialogView {
announceAccessibilityEvent();
};
protected FaceDialogView(Context context,
DialogViewCallback callback) {
super(context, callback);
protected FaceDialogView(Context context, DialogViewCallback callback, Injector injector) {
super(context, callback, injector);
mIconController = new IconController();
}
private void updateSize(int newSize) {
@VisibleForTesting
void updateSize(int newSize) {
final float padding = dpToPixels(IMPLICIT_Y_PADDING);
final float iconSmallPositionY = mDialog.getHeight() - mBiometricIcon.getHeight() - padding;

View File

@@ -33,9 +33,9 @@ public class FingerprintDialogView extends BiometricDialogView {
private static final String TAG = "FingerprintDialogView";
protected FingerprintDialogView(Context context,
DialogViewCallback callback) {
super(context, callback);
protected FingerprintDialogView(Context context, DialogViewCallback callback,
Injector injector) {
super(context, callback, injector);
}
@Override

View File

@@ -0,0 +1,204 @@
/*
* 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.ui;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.spy;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Bundle;
import android.os.UserManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.DialogViewCallback;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
public class BiometricDialogViewTest extends SysuiTestCase {
FaceDialogView mFaceDialogView;
private static final String TITLE = "Title";
private static final String SUBTITLE = "Subtitle";
private static final String DESCRIPTION = "Description";
private static final String NEGATIVE_BUTTON = "Negative Button";
private static final String TEST_HELP = "Help";
TestableContext mTestableContext;
@Mock
private DialogViewCallback mCallback;
@Mock
private UserManager mUserManager;
@Mock
private DevicePolicyManager mDpm;
private static class Injector extends BiometricDialogView.Injector {
@Override
public WakefulnessLifecycle getWakefulnessLifecycle() {
final WakefulnessLifecycle lifecycle = new WakefulnessLifecycle();
lifecycle.dispatchFinishedWakingUp();
return lifecycle;
}
}
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mTestableContext = spy(mContext);
mTestableContext.addMockSystemService(UserManager.class, mUserManager);
mTestableContext.addMockSystemService(DevicePolicyManager.class, mDpm);
}
@Test
public void testContentStates_confirmationRequired_authenticated() {
mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
true /* requireConfirmation */);
mFaceDialogView.onAttachedToWindow();
// When starting authentication
assertEquals(View.VISIBLE, mFaceDialogView.mTitleText.getVisibility());
assertEquals(View.VISIBLE, mFaceDialogView.mSubtitleText.getVisibility());
assertEquals(View.VISIBLE, mFaceDialogView.mDescriptionText.getVisibility());
assertEquals(View.INVISIBLE, mFaceDialogView.mErrorText.getVisibility());
assertEquals(View.VISIBLE, mFaceDialogView.mPositiveButton.getVisibility());
assertEquals(View.VISIBLE, mFaceDialogView.mNegativeButton.getVisibility());
assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility());
// Contents are as expected
assertTrue(TITLE.contentEquals(mFaceDialogView.mTitleText.getText()));
assertTrue(SUBTITLE.contentEquals(mFaceDialogView.mSubtitleText.getText()));
assertTrue(DESCRIPTION.contentEquals(mFaceDialogView.mDescriptionText.getText()));
assertTrue(mFaceDialogView.mPositiveButton.getText().toString()
.contentEquals(mContext.getString(R.string.biometric_dialog_confirm)));
assertTrue(NEGATIVE_BUTTON.contentEquals(mFaceDialogView.mNegativeButton.getText()));
assertTrue(mFaceDialogView.mTryAgainButton.getText().toString()
.contentEquals(mContext.getString(R.string.biometric_dialog_try_again)));
// When help message is received
mFaceDialogView.onHelp(TEST_HELP);
assertEquals(mFaceDialogView.mErrorText.getVisibility(), View.VISIBLE);
assertTrue(TEST_HELP.contentEquals(mFaceDialogView.mErrorText.getText()));
// When authenticated, confirm button comes out
mFaceDialogView.onAuthenticationSucceeded();
assertEquals(View.VISIBLE, mFaceDialogView.mPositiveButton.getVisibility());
assertEquals(true, mFaceDialogView.mPositiveButton.isEnabled());
}
@Test
public void testContentStates_confirmationNotRequired_authenticated() {
mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
false /* requireConfirmation */);
mFaceDialogView.onAttachedToWindow();
mFaceDialogView.updateSize(FaceDialogView.SIZE_SMALL);
assertEquals(View.INVISIBLE, mFaceDialogView.mTitleText.getVisibility());
assertNotSame(View.VISIBLE, mFaceDialogView.mSubtitleText.getVisibility());
assertNotSame(View.VISIBLE, mFaceDialogView.mDescriptionText.getVisibility());
assertEquals(View.INVISIBLE, mFaceDialogView.mErrorText.getVisibility());
assertEquals(View.GONE, mFaceDialogView.mPositiveButton.getVisibility());
assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility());
assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility());
}
@Test
public void testContentStates_confirmationNotRequired_help() {
mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
false /* requireConfirmation */);
mFaceDialogView.onAttachedToWindow();
mFaceDialogView.onHelp(TEST_HELP);
assertEquals(mFaceDialogView.mErrorText.getVisibility(), View.VISIBLE);
assertTrue(TEST_HELP.contentEquals(mFaceDialogView.mErrorText.getText()));
}
@Test
public void testBack_sendsUserCanceled() {
// TODO: Need robolectric framework to wait for handler to complete
}
@Test
public void testScreenOff_sendsUserCanceled() {
// TODO: Need robolectric framework to wait for handler to complete
}
@Test
public void testRestoreState_contentStatesCorrect() {
mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
false /* requireConfirmation */);
mFaceDialogView.onAttachedToWindow();
mFaceDialogView.onAuthenticationFailed(TEST_HELP);
final Bundle bundle = new Bundle();
mFaceDialogView.onSaveState(bundle);
mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
false /* requireConfirmation */);
mFaceDialogView.restoreState(bundle);
mFaceDialogView.onAttachedToWindow();
assertEquals(View.VISIBLE, mFaceDialogView.mTryAgainButton.getVisibility());
}
private FaceDialogView buildFaceDialogView(Context context, DialogViewCallback callback,
boolean requireConfirmation) {
return (FaceDialogView) new BiometricDialogView.Builder(context)
.setCallback(callback)
.setBiometricPromptBundle(createTestDialogBundle())
.setRequireConfirmation(requireConfirmation)
.setUserId(0)
.setOpPackageName("test_package")
.build(BiometricDialogView.Builder.TYPE_FACE, new Injector());
}
private Bundle createTestDialogBundle() {
Bundle bundle = new Bundle();
bundle.putCharSequence(BiometricPrompt.KEY_TITLE, TITLE);
bundle.putCharSequence(BiometricPrompt.KEY_SUBTITLE, SUBTITLE);
bundle.putCharSequence(BiometricPrompt.KEY_DESCRIPTION, DESCRIPTION);
bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, NEGATIVE_BUTTON);
// RequireConfirmation is a hint to BiometricService. This can be forced to be required
// by user settings, and should be tested in BiometricService.
bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true);
return bundle;
}
}