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

Commit a37e77f5 authored by Curtis Belmonte's avatar Curtis Belmonte Committed by Automerger Merge Worker
Browse files

Merge "Make BiometricPrompt honor max attempts before wipe" into rvc-dev am:...

Merge "Make BiometricPrompt honor max attempts before wipe" into rvc-dev am: b51645d6 am: 5ad2541c am: 5fd846ce am: 8d978279

Change-Id: I959b681b3f891e6b51e38e09cbf571706ec4624b
parents b435dfe3 8d978279
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -374,6 +374,17 @@
    <string name="biometric_dialog_wrong_password">Wrong password</string>
    <!-- Error string shown when the user enters too many incorrect attempts [CHAR LIMIT=120]-->
    <string name="biometric_dialog_credential_too_many_attempts">Too many incorrect attempts.\nTry again in <xliff:g id="number">%d</xliff:g> seconds.</string>
    <!-- Error string shown when the user enters an incorrect PIN/pattern/password and it counts towards the max attempts before the data on the device is wiped. [CHAR LIMIT=NONE]-->
    <string name="biometric_dialog_credential_attempts_before_wipe">Try again. Attempt <xliff:g id="attempts" example="1">%1$d</xliff:g> of <xliff:g id="max_attempts" example="3">%2$d</xliff:g>.</string>

    <!-- Content of a dialog shown when the user has failed to provide the device lock too many times and the device is wiped. [CHAR LIMIT=NONE] -->
    <string name="biometric_dialog_failed_attempts_now_wiping_device">Too many incorrect attempts. This device\u2019s data will be deleted.</string>
    <!-- Content of a dialog shown when the user has failed to provide the user lock too many times and the user is removed. [CHAR LIMIT=NONE] -->
    <string name="biometric_dialog_failed_attempts_now_wiping_user">Too many incorrect attempts. This user will be deleted.</string>
    <!-- Content of a dialog shown when the user has failed to provide the work lock too many times and the work profile is removed. [CHAR LIMIT=NONE] -->
    <string name="biometric_dialog_failed_attempts_now_wiping_profile">Too many incorrect attempts. This work profile and its data will be deleted.</string>
    <!-- Button label to dismiss the dialog telling the user the device, user, or work profile has been wiped. [CHAR LIMIT=40] -->
    <string name="biometric_dialog_now_wiping_dialog_dismiss">Dismiss</string>

    <!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication -->
    <string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string>
+120 −24
Original line number Diff line number Diff line
@@ -16,8 +16,10 @@

package com.android.systemui.biometrics;

import android.annotation.NonNull;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricPrompt;
import android.os.AsyncTask;
@@ -26,32 +28,49 @@ import android.os.CountDownTimer;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;

import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Abstract base class for Pin, Pattern, or Password authentication, for
 * {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)}}
 */
public abstract class AuthCredentialView extends LinearLayout {

    private static final String TAG = "BiometricPrompt/AuthCredentialView";
    private static final int ERROR_DURATION_MS = 3000;

    private final AccessibilityManager mAccessibilityManager;
    static final int USER_TYPE_PRIMARY = 1;
    static final int USER_TYPE_MANAGED_PROFILE = 2;
    static final int USER_TYPE_SECONDARY = 3;
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({USER_TYPE_PRIMARY, USER_TYPE_MANAGED_PROFILE, USER_TYPE_SECONDARY})
    private @interface UserType {}

    protected final Handler mHandler;
    protected final LockPatternUtils mLockPatternUtils;

    private final AccessibilityManager mAccessibilityManager;
    private final UserManager mUserManager;
    private final DevicePolicyManager mDevicePolicyManager;

    private Bundle mBiometricPromptBundle;
    private AuthPanelController mPanelController;
@@ -65,7 +84,6 @@ public abstract class AuthCredentialView extends LinearLayout {
    protected TextView mErrorView;

    protected @Utils.CredentialType int mCredentialType;
    protected final LockPatternUtils mLockPatternUtils;
    protected AuthContainerView mContainerView;
    protected Callback mCallback;
    protected AsyncTask<?, ?, ?> mPendingLockCheck;
@@ -106,15 +124,19 @@ public abstract class AuthCredentialView extends LinearLayout {

        @Override
        public void onFinish() {
            if (mErrorView != null) {
                mErrorView.setText("");
            }
        }
    }

    protected final Runnable mClearErrorRunnable = new Runnable() {
        @Override
        public void run() {
            if (mErrorView != null) {
                mErrorView.setText("");
            }
        }
    };

    public AuthCredentialView(Context context, AttributeSet attrs) {
@@ -123,13 +145,19 @@ public abstract class AuthCredentialView extends LinearLayout {
        mLockPatternUtils = new LockPatternUtils(mContext);
        mHandler = new Handler(Looper.getMainLooper());
        mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
        mUserManager = mContext.getSystemService(UserManager.class);
        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
    }

    protected void showError(String error) {
        if (mHandler != null) {
            mHandler.removeCallbacks(mClearErrorRunnable);
        mErrorView.setText(error);
            mHandler.postDelayed(mClearErrorRunnable, ERROR_DURATION_MS);
        }
        if (mErrorView != null) {
            mErrorView.setText(error);
        }
    }

    private void setTextOrHide(TextView view, CharSequence text) {
        if (TextUtils.isEmpty(text)) {
@@ -274,23 +302,91 @@ public abstract class AuthCredentialView extends LinearLayout {
                };
                mErrorTimer.start();
            } else {
                final int error;
                final boolean didUpdateErrorText = reportFailedAttempt();
                if (!didUpdateErrorText) {
                    final @StringRes int errorRes;
                    switch (mCredentialType) {
                        case Utils.CREDENTIAL_PIN:
                        error = R.string.biometric_dialog_wrong_pin;
                            errorRes = R.string.biometric_dialog_wrong_pin;
                            break;
                        case Utils.CREDENTIAL_PATTERN:
                        error = R.string.biometric_dialog_wrong_pattern;
                            errorRes = R.string.biometric_dialog_wrong_pattern;
                            break;
                        case Utils.CREDENTIAL_PASSWORD:
                        error = R.string.biometric_dialog_wrong_password;
                        break;
                        default:
                        error = R.string.biometric_dialog_wrong_password;
                            errorRes = R.string.biometric_dialog_wrong_password;
                            break;
                    }
                showError(getResources().getString(error));
                    showError(getResources().getString(errorRes));
                }
            }
        }
    }

    private boolean reportFailedAttempt() {
        boolean result = updateErrorMessage(
                mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
        mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
        return result;
    }

    private boolean updateErrorMessage(int numAttempts) {
        // Don't show any message if there's no maximum number of attempts.
        final int maxAttempts = mLockPatternUtils.getMaximumFailedPasswordsForWipe(
                mEffectiveUserId);
        if (maxAttempts <= 0 || numAttempts <= 0) {
            return false;
        }

        // Update the on-screen error string.
        if (mErrorView != null) {
            final String message = getResources().getString(
                    R.string.biometric_dialog_credential_attempts_before_wipe,
                    numAttempts,
                    maxAttempts);
            showError(message);
        }

        // Only show popup dialog before wipe.
        final int remainingAttempts = maxAttempts - numAttempts;
        if (remainingAttempts <= 0) {
            showNowWipingMessage();
            mContainerView.animateAway(AuthDialogCallback.DISMISSED_ERROR);
        }
        return true;
    }

    private void showNowWipingMessage() {
        final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
                .setMessage(getNowWipingMessageRes(getUserTypeForWipe()))
                .setPositiveButton(R.string.biometric_dialog_now_wiping_dialog_dismiss, null)
                .create();
        alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        alertDialog.show();
    }

    private @UserType int getUserTypeForWipe() {
        final UserInfo userToBeWiped = mUserManager.getUserInfo(
                mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(mEffectiveUserId));
        if (userToBeWiped == null || userToBeWiped.isPrimary()) {
            return USER_TYPE_PRIMARY;
        } else if (userToBeWiped.isManagedProfile()) {
            return USER_TYPE_MANAGED_PROFILE;
        } else {
            return USER_TYPE_SECONDARY;
        }
    }

    private static @StringRes int getNowWipingMessageRes(@UserType int userType) {
        switch (userType) {
            case USER_TYPE_PRIMARY:
                return R.string.biometric_dialog_failed_attempts_now_wiping_device;
            case USER_TYPE_MANAGED_PROFILE:
                return R.string.biometric_dialog_failed_attempts_now_wiping_profile;
            case USER_TYPE_SECONDARY:
                return R.string.biometric_dialog_failed_attempts_now_wiping_user;
            default:
                throw new IllegalArgumentException("Unrecognized user type:" + userType);
        }
    }