diff options
| author | 2019-08-09 17:35:41 -0700 | |
|---|---|---|
| committer | 2019-08-15 15:34:43 -0700 | |
| commit | 27fafb76551cfec876c01cfadfa7f9da6056312e (patch) | |
| tree | 11f34ba66f6f8fd210e217de680e2c43e27aec1f | |
| parent | 050315f61a27fc399e17a8396fa6b999ee98f211 (diff) | |
4/n: Make BiometricDialogView testable
Fixes: 138628043
Test: Manual BiometricPrompt test
Test: atest BiometricDialogViewTest
Change-Id: I105f44aad5ffe5f86a68ae688f7b56a5dd42c37d
4 files changed, 249 insertions, 22 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java index 66718c157bed..2b4dde55ef72 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java index cfa17ee68922..bd8714812ab4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java index 1c339ba6db54..e597080f89fd 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java new file mode 100644 index 000000000000..bbdd837bb446 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java @@ -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; + } +} |