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

Commit 27fafb76 authored by Kevin Chyn's avatar Kevin Chyn
Browse files

4/n: Make BiometricDialogView testable

Fixes: 138628043

Test: Manual BiometricPrompt test
Test: atest BiometricDialogViewTest

Change-Id: I105f44aad5ffe5f86a68ae688f7b56a5dd42c37d
parent 050315f6
Loading
Loading
Loading
Loading
+35 −14
Original line number Diff line number Diff line
@@ -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;
+7 −5
Original line number Diff line number Diff line
@@ -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;

+3 −3
Original line number Diff line number Diff line
@@ -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
+204 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}