From 8110ebb57c67556f6e6bdc43d60a5f33b8b17e57 Mon Sep 17 00:00:00 2001 From: Kevin Chyn Date: Wed, 2 Oct 2019 11:16:31 -0700 Subject: [PATCH] 16/n: Add PIN/Password Bug: 140127687 Make AuthCredentialView abstract, and have the following subclasses 1) AuthCredentialPatternView 2) AuthCredentialPasswordView Back button cancels password authentication Test: manual test with pattern, pin, password Test: atest com.android.systemui.biometrics Change-Id: I95e42144616a59827da25d10d063990452714f76 --- ...w.xml => auth_credential_pattern_view.xml} | 4 +- .../layout/auth_credential_password_view.xml | 101 ++++++++++++ ...w.xml => auth_credential_pattern_view.xml} | 4 +- .../biometrics/AuthBiometricView.java | 20 ++- .../biometrics/AuthContainerView.java | 18 ++- .../AuthCredentialPasswordView.java | 119 ++++++++++++++ .../biometrics/AuthCredentialPatternView.java | 102 ++++++++++++ .../biometrics/AuthCredentialView.java | 153 ++++++++---------- .../android/systemui/biometrics/Utils.java | 36 +++++ 9 files changed, 451 insertions(+), 106 deletions(-) rename packages/SystemUI/res/layout-land/{auth_credential_view.xml => auth_credential_pattern_view.xml} (97%) create mode 100644 packages/SystemUI/res/layout/auth_credential_password_view.xml rename packages/SystemUI/res/layout/{auth_credential_view.xml => auth_credential_pattern_view.xml} (96%) create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java diff --git a/packages/SystemUI/res/layout-land/auth_credential_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml similarity index 97% rename from packages/SystemUI/res/layout-land/auth_credential_view.xml rename to packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml index 4a34ede239203..c3fa39e5a87f5 100644 --- a/packages/SystemUI/res/layout-land/auth_credential_view.xml +++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml @@ -14,7 +14,7 @@ ~ limitations under the License. --> - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml new file mode 100644 index 0000000000000..4aed0333e9ca8 --- /dev/null +++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_credential_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml similarity index 96% rename from packages/SystemUI/res/layout/auth_credential_view.xml rename to packages/SystemUI/res/layout/auth_credential_pattern_view.xml index 74f991c24130a..c9edcd6062774 100644 --- a/packages/SystemUI/res/layout/auth_credential_view.xml +++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml @@ -14,7 +14,7 @@ ~ limitations under the License. --> - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index ecc1f695d4f59..69149737b599d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -629,19 +629,17 @@ public abstract class AuthBiometricView extends LinearLayout { final String negativeText; if (isDeviceCredentialAllowed()) { - final LockPatternUtils lpu = new LockPatternUtils(mContext); - switch (lpu.getKeyguardStoredPasswordQuality(mUserId)) { - case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: - negativeText = getResources().getString(R.string.biometric_dialog_use_pattern); - break; - case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: - case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: + + final @Utils.CredentialType int credentialType = + Utils.getCredentialType(mContext, mUserId); + switch(credentialType) { + case Utils.CREDENTIAL_PIN: negativeText = getResources().getString(R.string.biometric_dialog_use_pin); break; - case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: - case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: - case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: - case DevicePolicyManager.PASSWORD_QUALITY_MANAGED: + case Utils.CREDENTIAL_PATTERN: + negativeText = getResources().getString(R.string.biometric_dialog_use_pattern); + break; + case Utils.CREDENTIAL_PASSWORD: negativeText = getResources().getString(R.string.biometric_dialog_use_password); break; default: diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 7c6cb085a245f..ced910f4b600c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -307,8 +307,22 @@ public class AuthContainerView extends LinearLayout */ private void addCredentialView(boolean animatePanel, boolean animateContents) { final LayoutInflater factory = LayoutInflater.from(mContext); - mCredentialView = (AuthCredentialView) factory.inflate( - R.layout.auth_credential_view, null, false); + final int credentialType = Utils.getCredentialType(mContext, mConfig.mUserId); + switch (credentialType) { + case Utils.CREDENTIAL_PATTERN: + mCredentialView = (AuthCredentialView) factory.inflate( + R.layout.auth_credential_pattern_view, null, false); + break; + case Utils.CREDENTIAL_PIN: + case Utils.CREDENTIAL_PASSWORD: + mCredentialView = (AuthCredentialView) factory.inflate( + R.layout.auth_credential_password_view, null, false); + break; + default: + throw new IllegalStateException("Unknown credential type: " + credentialType); + } + + mCredentialView.setContainerView(this); mCredentialView.setUser(mConfig.mUserId); mCredentialView.setCallback(mCredentialCallback); mCredentialView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java new file mode 100644 index 0000000000000..99a40ba5ebf41 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java @@ -0,0 +1,119 @@ +/* + * 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 android.content.Context; +import android.text.InputType; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.TextView; + +import com.android.internal.widget.LockPatternChecker; +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.R; + +/** + * Pin and Password UI + */ +public class AuthCredentialPasswordView extends AuthCredentialView + implements TextView.OnEditorActionListener { + + private static final String TAG = "BiometricPrompt/AuthCredentialPasswordView"; + + private final InputMethodManager mImm; + private EditText mPasswordField; + + public AuthCredentialPasswordView(Context context, + AttributeSet attrs) { + super(context, attrs); + mImm = mContext.getSystemService(InputMethodManager.class); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mPasswordField = findViewById(R.id.lockPassword); + mPasswordField.setOnEditorActionListener(this); + + if (mCredentialType == Utils.CREDENTIAL_PIN) { + mPasswordField.setInputType( + InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD); + } + + mPasswordField.setOnKeyListener((v, keyCode, event) -> { + if (keyCode != KeyEvent.KEYCODE_BACK) { + return false; + } + if (event.getAction() == KeyEvent.ACTION_UP) { + mContainerView.animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); + } + return true; + }); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + // Wait a bit to focus the field so the focusable flag on the window is already set then. + post(() -> { + mPasswordField.requestFocus(); + mImm.showSoftInput(mPasswordField, InputMethodManager.SHOW_IMPLICIT); + }); + } + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + // Check if this was the result of hitting the enter key + final boolean isSoftImeEvent = event == null + && (actionId == EditorInfo.IME_NULL + || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT); + final boolean isKeyboardEnterKey = event != null + && KeyEvent.isConfirmKey(event.getKeyCode()) + && event.getAction() == KeyEvent.ACTION_DOWN; + if (isSoftImeEvent || isKeyboardEnterKey) { + checkPasswordAndUnlock(); + return true; + } + return false; + } + + private void checkPasswordAndUnlock() { + final byte[] password = LockPatternUtils.charSequenceToByteArray(mPasswordField.getText()); + if (password == null || password.length == 0) { + return; + } + + mPendingLockCheck = LockPatternChecker.checkPassword(mLockPatternUtils, + password, mUserId, this::onCredentialChecked); + } + + @Override + protected void onCredentialChecked(boolean matched, int timeoutMs) { + super.onCredentialChecked(matched, timeoutMs); + + if (matched) { + mImm.hideSoftInputFromWindow(getWindowToken(), 0 /* flags */); + } else { + mPasswordField.setText(""); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java new file mode 100644 index 0000000000000..6c36f82632376 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java @@ -0,0 +1,102 @@ +/* + * 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 android.content.Context; +import android.util.AttributeSet; + +import com.android.internal.widget.LockPatternChecker; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockPatternView; +import com.android.systemui.R; + +import java.util.List; + +/** + * Pattern UI + */ +public class AuthCredentialPatternView extends AuthCredentialView { + + private LockPatternView mLockPatternView; + + private class UnlockPatternListener implements LockPatternView.OnPatternListener { + + @Override + public void onPatternStart() { + + } + + @Override + public void onPatternCleared() { + + } + + @Override + public void onPatternCellAdded(List pattern) { + + } + + @Override + public void onPatternDetected(List pattern) { + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + } + + mLockPatternView.setEnabled(false); + + if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { + // Pattern size is less than the minimum, do not count it as a failed attempt. + onPatternChecked(false /* matched */, 0 /* timeoutMs */); + return; + } + + mPendingLockCheck = LockPatternChecker.checkPattern( + mLockPatternUtils, + pattern, + mUserId, + this::onPatternChecked); + } + + private void onPatternChecked(boolean matched, int timeoutMs) { + AuthCredentialPatternView.this.onCredentialChecked(matched, timeoutMs); + if (timeoutMs > 0) { + mLockPatternView.setEnabled(false); + } else { + mLockPatternView.setEnabled(true); + } + } + } + + @Override + protected void onErrorTimeoutFinish() { + super.onErrorTimeoutFinish(); + mLockPatternView.setEnabled(true); + } + + public AuthCredentialPatternView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mLockPatternView = findViewById(R.id.lockPattern); + mLockPatternView.setOnPatternListener(new UnlockPatternListener()); + mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(mUserId)); + mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java index 1ba88c6add097..1c75ae2dccde4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java @@ -31,31 +31,23 @@ import android.view.accessibility.AccessibilityManager; import android.widget.LinearLayout; import android.widget.TextView; -import com.android.internal.widget.LockPatternChecker; import com.android.internal.widget.LockPatternUtils; -import com.android.internal.widget.LockPatternView; import com.android.systemui.Interpolators; import com.android.systemui.R; -import java.util.List; - /** - * Shows Pin, Pattern, or Password for + * Abstract base class for Pin, Pattern, or Password authentication, for * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} */ -public class AuthCredentialView extends LinearLayout { +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; - private final LockPatternUtils mLockPatternUtils; - private final Handler mHandler; - private LockPatternView mLockPatternView; - private int mUserId; - private AsyncTask mPendingLockCheck; - private Callback mCallback; - private ErrorTimer mErrorTimer; + protected final Handler mHandler; + private Bundle mBiometricPromptBundle; private AuthPanelController mPanelController; private boolean mShouldAnimatePanel; @@ -64,13 +56,21 @@ public class AuthCredentialView extends LinearLayout { private TextView mTitleView; private TextView mSubtitleView; private TextView mDescriptionView; - private TextView mErrorView; + protected TextView mErrorView; + + protected @Utils.CredentialType int mCredentialType; + protected final LockPatternUtils mLockPatternUtils; + protected AuthContainerView mContainerView; + protected Callback mCallback; + protected AsyncTask mPendingLockCheck; + protected int mUserId; + protected ErrorTimer mErrorTimer; interface Callback { void onCredentialMatched(); } - private static class ErrorTimer extends CountDownTimer { + protected static class ErrorTimer extends CountDownTimer { private final TextView mErrorView; private final Context mContext; @@ -102,74 +102,7 @@ public class AuthCredentialView extends LinearLayout { } } - private class UnlockPatternListener implements LockPatternView.OnPatternListener { - - @Override - public void onPatternStart() { - - } - - @Override - public void onPatternCleared() { - - } - - @Override - public void onPatternCellAdded(List pattern) { - - } - - @Override - public void onPatternDetected(List pattern) { - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - } - - mLockPatternView.setEnabled(false); - - if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { - // Pattern size is less than the minimum, do not count it as a failed attempt. - onPatternChecked(false /* matched */, 0 /* timeoutMs */); - return; - } - - mPendingLockCheck = LockPatternChecker.checkPattern( - mLockPatternUtils, - pattern, - mUserId, - this::onPatternChecked); - } - - private void onPatternChecked(boolean matched, int timeoutMs) { - mLockPatternView.setEnabled(true); - - if (matched) { - mClearErrorRunnable.run(); - mCallback.onCredentialMatched(); - } else { - if (timeoutMs > 0) { - mHandler.removeCallbacks(mClearErrorRunnable); - mLockPatternView.setEnabled(false); - long deadline = mLockPatternUtils.setLockoutAttemptDeadline(mUserId, timeoutMs); - mErrorTimer = new ErrorTimer(mContext, - deadline - SystemClock.elapsedRealtime(), - LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS, - mErrorView) { - @Override - public void onFinish() { - mClearErrorRunnable.run(); - mLockPatternView.setEnabled(true); - } - }; - mErrorTimer.start(); - } else { - showError(getResources().getString(R.string.biometric_dialog_wrong_pattern)); - } - } - } - } - - private final Runnable mClearErrorRunnable = new Runnable() { + protected final Runnable mClearErrorRunnable = new Runnable() { @Override public void run() { mErrorView.setText(""); @@ -179,12 +112,12 @@ public class AuthCredentialView extends LinearLayout { public AuthCredentialView(Context context, AttributeSet attrs) { super(context, attrs); - mHandler = new Handler(Looper.getMainLooper()); mLockPatternUtils = new LockPatternUtils(mContext); + mHandler = new Handler(Looper.getMainLooper()); mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); } - private void showError(String error) { + protected void showError(String error) { mHandler.removeCallbacks(mClearErrorRunnable); mErrorView.setText(error); mHandler.postDelayed(mClearErrorRunnable, ERROR_DURATION_MS); @@ -225,6 +158,10 @@ public class AuthCredentialView extends LinearLayout { mShouldAnimateContents = animateContents; } + void setContainerView(AuthContainerView containerView) { + mContainerView = containerView; + } + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -263,14 +200,11 @@ public class AuthCredentialView extends LinearLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); + mCredentialType = Utils.getCredentialType(mContext, mUserId); mTitleView = findViewById(R.id.title); mSubtitleView = findViewById(R.id.subtitle); mDescriptionView = findViewById(R.id.description); mErrorView = findViewById(R.id.error); - mLockPatternView = findViewById(R.id.lockPattern); - mLockPatternView.setOnPatternListener(new UnlockPatternListener()); - mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(mUserId)); - mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); } @Override @@ -286,4 +220,45 @@ public class AuthCredentialView extends LinearLayout { } } + protected void onErrorTimeoutFinish() {} + + protected void onCredentialChecked(boolean matched, int timeoutMs) { + if (matched) { + mClearErrorRunnable.run(); + mCallback.onCredentialMatched(); + } else { + if (timeoutMs > 0) { + mHandler.removeCallbacks(mClearErrorRunnable); + long deadline = mLockPatternUtils.setLockoutAttemptDeadline(mUserId, timeoutMs); + mErrorTimer = new ErrorTimer(mContext, + deadline - SystemClock.elapsedRealtime(), + LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS, + mErrorView) { + @Override + public void onFinish() { + onErrorTimeoutFinish(); + mClearErrorRunnable.run(); + } + }; + mErrorTimer.start(); + } else { + final int error; + switch (mCredentialType) { + case Utils.CREDENTIAL_PIN: + error = R.string.biometric_dialog_wrong_pin; + break; + case Utils.CREDENTIAL_PATTERN: + error = 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; + break; + } + showError(getResources().getString(error)); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java index 485e667aae985..b78a9ad274ebd 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java @@ -18,6 +18,8 @@ package com.android.systemui.biometrics; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE; +import android.annotation.IntDef; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.hardware.biometrics.Authenticator; import android.hardware.biometrics.BiometricPrompt; @@ -28,7 +30,23 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.R; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + public class Utils { + + public static final int CREDENTIAL_PIN = 1; + public static final int CREDENTIAL_PATTERN = 2; + public static final int CREDENTIAL_PASSWORD = 3; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD}) + @interface CredentialType {} + + static float dpToPixels(Context context, float dp) { return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT); @@ -63,4 +81,22 @@ public class Utils { static int getAuthenticators(Bundle biometricPromptBundle) { return biometricPromptBundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED); } + + static @CredentialType int getCredentialType(Context context, int userId) { + final LockPatternUtils lpu = new LockPatternUtils(context); + switch (lpu.getKeyguardStoredPasswordQuality(userId)) { + case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: + return CREDENTIAL_PATTERN; + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: + return CREDENTIAL_PIN; + case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: + case DevicePolicyManager.PASSWORD_QUALITY_MANAGED: + return CREDENTIAL_PASSWORD; + default: + return CREDENTIAL_PASSWORD; + } + } }