Visualize password requirements and their fulfillment

1. Aggregate policies and generate the requirements
2. When user modifies the password, check is each requirement fulfilled
   Update the view accordingly.

Change-Id: I962ed3b81ce844006be1024a493e94ce52a3fdec
Fix: 24900754
This commit is contained in:
Tony Mak
2016-07-12 11:19:45 +01:00
parent 9a8e5a7f25
commit 0bbcdccdbf
8 changed files with 542 additions and 186 deletions

View File

@@ -0,0 +1,24 @@
<!-- Copyright (C) 2016 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"
android:fillColor="#0F9D58"/>
</vector>

View File

@@ -0,0 +1,24 @@
<!-- Copyright (C) 2016 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"
android:fillColor="#757575"/>
</vector>

View File

@@ -14,7 +14,6 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<com.android.setupwizardlib.GlifLayout <com.android.setupwizardlib.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto" xmlns:settings="http://schemas.android.com/apk/res-auto"
@@ -31,9 +30,11 @@
android:orientation="vertical"> android:orientation="vertical">
<!-- header text ('Enter Pin') --> <!-- header text ('Enter Pin') -->
<TextView android:id="@+id/headerText" <TextView
android:id="@+id/headerText"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:accessibilityLiveRegion="polite"
android:gravity="center" android:gravity="center"
android:lines="2" android:lines="2"
android:textAppearance="?android:attr/textAppearanceMedium"/> android:textAppearance="?android:attr/textAppearanceMedium"/>
@@ -50,32 +51,43 @@
style="@style/TextAppearance.PasswordEntry"/> style="@style/TextAppearance.PasswordEntry"/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:id="@+id/bottom_container"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:clipChildren="false" android:layout_height="match_parent"
android:clipToPadding="false" android:orientation="vertical">
android:gravity="end"
android:orientation="horizontal">
<!-- left : cancel --> <android.support.v7.widget.RecyclerView
<Button android:id="@+id/cancel_button" android:id="@+id/password_requirements_view"
style="@style/SetupWizardButton.Negative" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/lockpassword_cancel_label" /> android:clipChildren="false"
android:clipToPadding="false"
android:gravity="end"
android:orientation="horizontal">
<Space <!-- left : cancel -->
android:layout_width="0dp" <Button android:id="@+id/cancel_button"
android:layout_height="0dp" style="@style/SetupWizardButton.Negative"
android:layout_weight="1" /> android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/lockpassword_cancel_label" />
<!-- right : continue --> <Space
<Button android:id="@+id/next_button" android:layout_width="0dp"
style="@style/SetupWizardButton.Positive" android:layout_height="0dp"
android:layout_width="wrap_content" android:layout_weight="1" />
android:layout_height="wrap_content"
android:text="@string/lockpassword_continue_label" />
<!-- right : continue -->
<Button android:id="@+id/next_button"
style="@style/SetupWizardButton.Positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/lockpassword_continue_label" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<!-- Spacer between password entry and keyboard --> <!-- Spacer between password entry and keyboard -->

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 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.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/description_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="4dp"
android:textSize="14sp"/>

View File

@@ -305,4 +305,9 @@
<dimen name="support_tile_min_height">48dp</dimen> <dimen name="support_tile_min_height">48dp</dimen>
<!-- support spacer layout height --> <!-- support spacer layout height -->
<dimen name="support_spacer_height">8dp</dimen> <dimen name="support_spacer_height">8dp</dimen>
<dimen name="password_requirement_textsize">14sp</dimen>
<!-- Visible vertical space we want to show below password edittext field when ime is shown.
The unit is sp as it is related to the text size of password requirement item. -->
<dimen name="visible_vertical_space_below_password">20sp</dimen>
</resources> </resources>

View File

@@ -1166,7 +1166,7 @@
<string name="lock_profile_wipe_dismiss">Dismiss</string> <string name="lock_profile_wipe_dismiss">Dismiss</string>
<!-- Hint shown in dialog screen when password is too short --> <!-- Hint shown in dialog screen when password is too short -->
<string name="lockpassword_password_too_short">Password must be at least %d characters</string> <string name="lockpassword_password_too_short">Must be at least %d characters</string>
<!-- Hint shown in dialog screen when PIN is too short --> <!-- Hint shown in dialog screen when PIN is too short -->
<string name="lockpassword_pin_too_short">PIN must be at least %d digits</string> <string name="lockpassword_pin_too_short">PIN must be at least %d digits</string>
@@ -1174,62 +1174,62 @@
<string name="lockpassword_continue_label">Continue</string> <string name="lockpassword_continue_label">Continue</string>
<!-- Error shown in popup when password is too long --> <!-- Error shown in popup when password is too long -->
<string name="lockpassword_password_too_long">Password must be fewer than <xliff:g id="number" example="17">%d</xliff:g> characters.</string> <string name="lockpassword_password_too_long">Must be fewer than <xliff:g id="number" example="17">%d</xliff:g> characters.</string>
<!-- Error shown in popup when PIN is too long --> <!-- Error shown in popup when PIN is too long -->
<string name="lockpassword_pin_too_long">PIN must be fewer than <xliff:g id="number" example="17">%d</xliff:g> digits.</string> <string name="lockpassword_pin_too_long">Must be fewer than <xliff:g id="number" example="17">%d</xliff:g> digits.</string>
<!-- Error shown when in PIN mode and user enters a non-digit --> <!-- Error shown when in PIN mode and user enters a non-digit -->
<string name="lockpassword_pin_contains_non_digits">PIN must contain only digits 0-9.</string> <string name="lockpassword_pin_contains_non_digits">Must contain only digits 0-9.</string>
<!-- Error shown when in PIN mode and PIN has been used recently. Please keep this string short! --> <!-- Error shown when in PIN mode and PIN has been used recently. Please keep this string short! -->
<string name="lockpassword_pin_recently_used">Device administrator doesn\u2019t allow using a recent PIN.</string> <string name="lockpassword_pin_recently_used">Device administrator doesn\u2019t allow using a recent PIN.</string>
<!-- Error shown when in PASSWORD mode and user enters an invalid character --> <!-- Error shown when in PASSWORD mode and user enters an invalid character -->
<string name="lockpassword_illegal_character">Password contains an illegal character.</string> <string name="lockpassword_illegal_character">This can\'t include an invalid character</string>
<!-- Error shown when in PASSWORD mode and password is all digits --> <!-- Error shown when in PASSWORD mode and password is all digits -->
<string name="lockpassword_password_requires_alpha">Password must contain at least one letter.</string> <string name="lockpassword_password_requires_alpha">Must contain at least one letter</string>
<!-- Error shown when in PASSWORD mode and password doesn't contain any digits --> <!-- Error shown when in PASSWORD mode and password doesn't contain any digits -->
<string name="lockpassword_password_requires_digit">Password must contain at least one digit.</string> <string name="lockpassword_password_requires_digit">Must contain at least one digit</string>
<!-- Error shown when in PASSWORD mode and password doesn't contain any symbols --> <!-- Error shown when in PASSWORD mode and password doesn't contain any symbols -->
<string name="lockpassword_password_requires_symbol">Password must contain at least one symbol.</string> <string name="lockpassword_password_requires_symbol">Must contain at least one symbol</string>
<!-- Error shown when in PASSWORD mode and password doesn't contain the required number of letters --> <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of letters -->
<plurals name="lockpassword_password_requires_letters"> <plurals name="lockpassword_password_requires_letters">
<item quantity="one">Password must contain at least 1 letter.</item> <item quantity="one">Must contain at least 1 letter</item>
<item quantity="other">Password must contain at least %d letters.</item> <item quantity="other">Must contain at least %d letters</item>
</plurals> </plurals>
<!-- Error shown when in PASSWORD mode and password doesn't contain the required number of lowercase letters --> <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of lowercase letters -->
<plurals name="lockpassword_password_requires_lowercase"> <plurals name="lockpassword_password_requires_lowercase">
<item quantity="one">Password must contain at least 1 lowercase letter.</item> <item quantity="one">Must contain at least 1 lowercase letter</item>
<item quantity="other">Password must contain at least %d lowercase letters.</item> <item quantity="other">Must contain at least %d lowercase letters</item>
</plurals> </plurals>
<!-- Error shown when in PASSWORD mode and password doesn't contain the required number of uppercase letters --> <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of uppercase letters -->
<plurals name="lockpassword_password_requires_uppercase"> <plurals name="lockpassword_password_requires_uppercase">
<item quantity="one">Password must contain at least 1 uppercase letter.</item> <item quantity="one">Must contain at least 1 uppercase letter</item>
<item quantity="other">Password must contain at least %d uppercase letters.</item> <item quantity="other">Must contain at least %d uppercase letters</item>
</plurals> </plurals>
<!-- Error shown when in PASSWORD mode and password doesn't contain the required number of numerical digits --> <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of numerical digits -->
<plurals name="lockpassword_password_requires_numeric"> <plurals name="lockpassword_password_requires_numeric">
<item quantity="one">Password must contain at least 1 numerical digit.</item> <item quantity="one">Must contain at least 1 numerical digit</item>
<item quantity="other">Password must contain at least %d numerical digits.</item> <item quantity="other">Must contain at least %d numerical digits</item>
</plurals> </plurals>
<!-- Error shown when in PASSWORD mode and password doesn't contain the required number of special symbols --> <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of special symbols -->
<plurals name="lockpassword_password_requires_symbols"> <plurals name="lockpassword_password_requires_symbols">
<item quantity="one">Password must contain at least 1 special symbol.</item> <item quantity="one">Must contain at least 1 special symbol</item>
<item quantity="other">Password must contain at least %d special symbols.</item> <item quantity="other">Must contain at least %d special symbols</item>
</plurals> </plurals>
<!-- Error shown when in PASSWORD mode and password doesn't contain the required number of non-letter characters --> <!-- Error shown when in PASSWORD mode and password doesn't contain the required number of non-letter characters -->
<plurals name="lockpassword_password_requires_nonletter"> <plurals name="lockpassword_password_requires_nonletter">
<item quantity="one">Password must contain at least 1 non-letter character.</item> <item quantity="one">Must contain at least 1 non-letter character</item>
<item quantity="other">Password must contain at least %d non-letter characters.</item> <item quantity="other">Must contain at least %d non-letter characters</item>
</plurals> </plurals>
<!-- Error shown when in PASSWORD mode and password has been used recently. Please keep this string short! --> <!-- Error shown when in PASSWORD mode and password has been used recently. Please keep this string short! -->

View File

@@ -21,10 +21,11 @@ import android.app.Fragment;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.InsetDrawable;
import android.inputmethodservice.KeyboardView; import android.inputmethodservice.KeyboardView;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.support.v7.widget.LinearLayoutManager;
import android.os.Message; import android.support.v7.widget.RecyclerView;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.Selection; import android.text.Selection;
@@ -32,13 +33,15 @@ import android.text.Spannable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Log; import android.util.Log;
import android.view.inputmethod.EditorInfo;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo; import android.view.ViewGroup.MarginLayoutParams;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener; import android.widget.TextView.OnEditorActionListener;
@@ -50,8 +53,18 @@ import com.android.internal.widget.PasswordEntryKeyboardHelper;
import com.android.internal.widget.PasswordEntryKeyboardView; import com.android.internal.widget.PasswordEntryKeyboardView;
import com.android.internal.widget.TextViewInputDisabler; import com.android.internal.widget.TextViewInputDisabler;
import com.android.settings.notification.RedactionInterstitial; import com.android.settings.notification.RedactionInterstitial;
import com.android.settings.password.PasswordRequirementAdapter;
import com.android.setupwizardlib.GlifLayout; import com.android.setupwizardlib.GlifLayout;
import java.util.ArrayList;
import java.util.List;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
public class ChooseLockPassword extends SettingsActivity { public class ChooseLockPassword extends SettingsActivity {
public static final String PASSWORD_MIN_KEY = "lockscreen.password_min"; public static final String PASSWORD_MIN_KEY = "lockscreen.password_min";
public static final String PASSWORD_MAX_KEY = "lockscreen.password_max"; public static final String PASSWORD_MAX_KEY = "lockscreen.password_max";
@@ -149,7 +162,7 @@ public class ChooseLockPassword extends SettingsActivity {
} }
public static class ChooseLockPasswordFragment extends InstrumentedFragment public static class ChooseLockPasswordFragment extends InstrumentedFragment
implements OnClickListener, OnEditorActionListener, TextWatcher, implements OnClickListener, OnEditorActionListener, TextWatcher,
SaveAndFinishWorker.Listener { SaveAndFinishWorker.Listener {
private static final String KEY_FIRST_PIN = "first_pin"; private static final String KEY_FIRST_PIN = "first_pin";
private static final String KEY_UI_STAGE = "ui_stage"; private static final String KEY_UI_STAGE = "ui_stage";
@@ -160,7 +173,7 @@ public class ChooseLockPassword extends SettingsActivity {
private String mChosenPassword; private String mChosenPassword;
private boolean mHasChallenge; private boolean mHasChallenge;
private long mChallenge; private long mChallenge;
private TextView mPasswordEntry; private EditText mPasswordEntry;
private TextViewInputDisabler mPasswordEntryInputDisabler; private TextViewInputDisabler mPasswordEntryInputDisabler;
private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE; private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
private int mPasswordMaxLength = 16; private int mPasswordMaxLength = 16;
@@ -170,35 +183,53 @@ public class ChooseLockPassword extends SettingsActivity {
private int mPasswordMinSymbols = 0; private int mPasswordMinSymbols = 0;
private int mPasswordMinNumeric = 0; private int mPasswordMinNumeric = 0;
private int mPasswordMinNonLetter = 0; private int mPasswordMinNonLetter = 0;
private int mUserId;
private boolean mHideDrawer = false;
/**
* Password requirements that we need to verify.
*/
private int[] mPasswordRequirements;
private LockPatternUtils mLockPatternUtils; private LockPatternUtils mLockPatternUtils;
private SaveAndFinishWorker mSaveAndFinishWorker; private SaveAndFinishWorker mSaveAndFinishWorker;
private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
private ChooseLockSettingsHelper mChooseLockSettingsHelper; private ChooseLockSettingsHelper mChooseLockSettingsHelper;
private Stage mUiStage = Stage.Introduction; private Stage mUiStage = Stage.Introduction;
private PasswordRequirementAdapter mPasswordRequirementAdapter;
private TextView mHeaderText; private TextView mHeaderText;
private String mFirstPin; private String mFirstPin;
private RecyclerView mPasswordRestrictionView;
private KeyboardView mKeyboardView; private KeyboardView mKeyboardView;
private PasswordEntryKeyboardHelper mKeyboardHelper; private PasswordEntryKeyboardHelper mKeyboardHelper;
private boolean mIsAlphaMode; private boolean mIsAlphaMode;
private Button mCancelButton; private Button mCancelButton;
private Button mNextButton; private Button mNextButton;
private static final int CONFIRM_EXISTING_REQUEST = 58; private static final int CONFIRM_EXISTING_REQUEST = 58;
static final int RESULT_FINISHED = RESULT_FIRST_USER; static final int RESULT_FINISHED = RESULT_FIRST_USER;
private static final long ERROR_MESSAGE_TIMEOUT = 3000;
private static final int MSG_SHOW_ERROR = 1;
private int mUserId; private static final int MIN_LETTER_IN_PASSWORD = 0;
private boolean mHideDrawer = false; private static final int MIN_UPPER_LETTERS_IN_PASSWORD = 1;
private static final int MIN_LOWER_LETTERS_IN_PASSWORD = 2;
private static final int MIN_SYMBOLS_IN_PASSWORD = 3;
private static final int MIN_NUMBER_IN_PASSWORD = 4;
private static final int MIN_NON_LETTER_IN_PASSWORD = 5;
private Handler mHandler = new Handler() { // Error code returned from {@link #validatePassword(String)}.
@Override private static final int NO_ERROR = 0;
public void handleMessage(Message msg) { private static final int CONTAIN_INVALID_CHARACTERS = 1 << 0;
if (msg.what == MSG_SHOW_ERROR) { private static final int TOO_SHORT = 1 << 1;
updateStage((Stage) msg.obj); private static final int TOO_LONG = 1 << 2;
} private static final int CONTAIN_NON_DIGITS = 1 << 3;
} private static final int CONTAIN_SEQUENTIAL_DIGITS = 1 << 4;
}; private static final int RECENTLY_USED = 1 << 5;
private static final int NOT_ENOUGH_LETTER = 1 << 6;
private static final int NOT_ENOUGH_UPPER_CASE = 1 << 7;
private static final int NOT_ENOUGH_LOWER_CASE = 1 << 8;
private static final int NOT_ENOUGH_DIGITS = 1 << 9;
private static final int NOT_ENOUGH_SYMBOLS = 1 << 10;
private static final int NOT_ENOUGH_NON_LETTER = 1 << 11;
/** /**
* Keep track internally of where the user is in choosing a pattern. * Keep track internally of where the user is in choosing a pattern.
@@ -243,33 +274,7 @@ public class ChooseLockPassword extends SettingsActivity {
} }
// Only take this argument into account if it belongs to the current profile. // Only take this argument into account if it belongs to the current profile.
mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras()); mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, processPasswordRequirements(intent);
mRequestedQuality), mLockPatternUtils.getRequestedPasswordQuality(
mUserId));
mPasswordMinLength = Math.max(Math.max(
LockPatternUtils.MIN_LOCK_PASSWORD_SIZE,
intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)),
mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId));
mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength);
mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY,
mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters(
mUserId));
mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY,
mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase(
mUserId));
mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY,
mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase(
mUserId));
mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY,
mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric(
mUserId));
mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY,
mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols(
mUserId));
mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY,
mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter(
mUserId));
mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity()); mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
mHideDrawer = getActivity().getIntent().getBooleanExtra(EXTRA_HIDE_DRAWER, false); mHideDrawer = getActivity().getIntent().getBooleanExtra(EXTRA_HIDE_DRAWER, false);
@@ -305,8 +310,12 @@ public class ChooseLockPassword extends SettingsActivity {
mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
|| DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality
|| DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality; || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality;
setupPasswordRequirementsView(view);
mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard); mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard);
mPasswordEntry = (TextView) view.findViewById(R.id.password_entry); mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
mPasswordEntry = (EditText) view.findViewById(R.id.password_entry);
mPasswordEntry.setOnEditorActionListener(this); mPasswordEntry.setOnEditorActionListener(this);
mPasswordEntry.addTextChangedListener(this); mPasswordEntry.addTextChangedListener(this);
mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry); mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
@@ -356,6 +365,24 @@ public class ChooseLockPassword extends SettingsActivity {
mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag( mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
FRAGMENT_TAG_SAVE_AND_FINISH); FRAGMENT_TAG_SAVE_AND_FINISH);
} }
// Workaround to show one password requirement below EditText when IME is shown.
// By adding an inset to the edit text background, we make the EditText occupy more
// vertical space, and the keyboard will then avoid hiding it. We have also set
// negative margin in the layout below in order to have them show in the correct
// position.
final int visibleVerticalSpaceBelowPassword =
getResources().getDimensionPixelOffset(
R.dimen.visible_vertical_space_below_password);
InsetDrawable drawable =
new InsetDrawable(
mPasswordEntry.getBackground(), 0, 0, 0, visibleVerticalSpaceBelowPassword);
mPasswordEntry.setBackgroundDrawable(drawable);
LinearLayout bottomContainer = (LinearLayout) view.findViewById(R.id.bottom_container);
LinearLayout.LayoutParams bottomContainerLp =
(LinearLayout.LayoutParams) bottomContainer.getLayoutParams();
bottomContainerLp.setMargins(0, -visibleVerticalSpaceBelowPassword, 0, 0);
if (activity instanceof SettingsActivity) { if (activity instanceof SettingsActivity) {
final SettingsActivity sa = (SettingsActivity) activity; final SettingsActivity sa = (SettingsActivity) activity;
int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header
@@ -366,6 +393,55 @@ public class ChooseLockPassword extends SettingsActivity {
} }
} }
private void setupPasswordRequirementsView(View view) {
// Construct passwordRequirements and requirementDescriptions.
List<Integer> passwordRequirements = new ArrayList<>();
List<String> requirementDescriptions = new ArrayList<>();
if (mPasswordMinUpperCase > 0) {
passwordRequirements.add(MIN_UPPER_LETTERS_IN_PASSWORD);
requirementDescriptions.add(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase,
mPasswordMinUpperCase));
}
if (mPasswordMinLowerCase > 0) {
passwordRequirements.add(MIN_LOWER_LETTERS_IN_PASSWORD);
requirementDescriptions.add(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase,
mPasswordMinLowerCase));
}
if (mPasswordMinLetters > 0) {
passwordRequirements.add(MIN_LETTER_IN_PASSWORD);
requirementDescriptions.add(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
mPasswordMinLetters));
}
if (mPasswordMinNumeric > 0) {
passwordRequirements.add(MIN_NUMBER_IN_PASSWORD);
requirementDescriptions.add(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric,
mPasswordMinNumeric));
}
if (mPasswordMinSymbols > 0) {
passwordRequirements.add(MIN_SYMBOLS_IN_PASSWORD);
requirementDescriptions.add(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols,
mPasswordMinSymbols));
}
if (mPasswordMinNonLetter > 0) {
passwordRequirements.add(MIN_NON_LETTER_IN_PASSWORD);
requirementDescriptions.add(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter,
mPasswordMinNonLetter));
}
// Convert list to array.
mPasswordRequirements = passwordRequirements.stream().mapToInt(i -> i).toArray();
mPasswordRestrictionView =
(RecyclerView) view.findViewById(R.id.password_requirements_view);
mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
mPasswordRequirementAdapter = new PasswordRequirementAdapter();
mPasswordRestrictionView.setAdapter(mPasswordRequirementAdapter);
}
@Override @Override
protected int getMetricsCategory() { protected int getMetricsCategory() {
return MetricsEvent.CHOOSE_LOCK_PASSWORD; return MetricsEvent.CHOOSE_LOCK_PASSWORD;
@@ -384,11 +460,9 @@ public class ChooseLockPassword extends SettingsActivity {
@Override @Override
public void onPause() { public void onPause() {
mHandler.removeMessages(MSG_SHOW_ERROR);
if (mSaveAndFinishWorker != null) { if (mSaveAndFinishWorker != null) {
mSaveAndFinishWorker.setListener(null); mSaveAndFinishWorker.setListener(null);
} }
super.onPause(); super.onPause();
} }
@@ -434,21 +508,95 @@ public class ChooseLockPassword extends SettingsActivity {
} }
/** /**
* Validates PIN and returns a message to display if PIN fails test. * Read the requirements from {@link DevicePolicyManager} and intent and aggregate them.
* @param password the raw password the user typed in *
* @return error message to show to user or null if password is OK * @param intent the incoming intent
*/ */
private String validatePassword(String password) { private void processPasswordRequirements(Intent intent) {
final int dpmPasswordQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId);
mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
mRequestedQuality), dpmPasswordQuality);
mPasswordMinLength = Math.max(Math.max(
LockPatternUtils.MIN_LOCK_PASSWORD_SIZE,
intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)),
mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId));
mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength);
mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY,
mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters(
mUserId));
mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY,
mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase(
mUserId));
mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY,
mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase(
mUserId));
mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY,
mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric(
mUserId));
mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY,
mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols(
mUserId));
mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY,
mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter(
mUserId));
// Modify the value based on dpm policy.
switch (dpmPasswordQuality) {
case PASSWORD_QUALITY_ALPHABETIC:
if (mPasswordMinLetters == 0) {
mPasswordMinLetters = 1;
}
break;
case PASSWORD_QUALITY_ALPHANUMERIC:
if (mPasswordMinLetters == 0) {
mPasswordMinLetters = 1;
}
if (mPasswordMinNumeric == 0) {
mPasswordMinNumeric = 1;
}
break;
case PASSWORD_QUALITY_COMPLEX:
// Reserve all the requirements.
break;
default:
mPasswordMinNumeric = 0;
mPasswordMinLetters = 0;
mPasswordMinUpperCase = 0;
mPasswordMinLowerCase = 0;
mPasswordMinSymbols = 0;
mPasswordMinNonLetter = 0;
}
}
/**
* Validates PIN and returns the validation result.
*
* @param password the raw password the user typed in
* @return the validation result.
*/
private int validatePassword(String password) {
int errorCode = NO_ERROR;
if (password.length() < mPasswordMinLength) { if (password.length() < mPasswordMinLength) {
return getString(mIsAlphaMode ? errorCode |= TOO_SHORT;
R.string.lockpassword_password_too_short } else if (password.length() > mPasswordMaxLength) {
: R.string.lockpassword_pin_too_short, mPasswordMinLength); errorCode |= TOO_LONG;
} } else {
if (password.length() > mPasswordMaxLength) { // The length requirements are fulfilled.
return getString(mIsAlphaMode ? if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
R.string.lockpassword_password_too_long // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
: R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1); final int sequence = LockPatternUtils.maxLengthSequence(password);
if (sequence > LockPatternUtils.MAX_ALLOWED_SEQUENCE) {
errorCode |= CONTAIN_SEQUENTIAL_DIGITS;
}
}
// Is the password recently used?
if (mLockPatternUtils.checkPasswordHistory(password, mUserId)) {
errorCode |= RECENTLY_USED;
}
} }
// Count different types of character.
int letters = 0; int letters = 0;
int numbers = 0; int numbers = 0;
int lowercase = 0; int lowercase = 0;
@@ -459,7 +607,8 @@ public class ChooseLockPassword extends SettingsActivity {
char c = password.charAt(i); char c = password.charAt(i);
// allow non control Latin-1 characters only // allow non control Latin-1 characters only
if (c < 32 || c > 127) { if (c < 32 || c > 127) {
return getString(R.string.lockpassword_illegal_character); errorCode |= CONTAIN_INVALID_CHARACTERS;
continue;
} }
if (c >= '0' && c <= '9') { if (c >= '0' && c <= '9') {
numbers++; numbers++;
@@ -475,63 +624,53 @@ public class ChooseLockPassword extends SettingsActivity {
nonletter++; nonletter++;
} }
} }
if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC == mRequestedQuality
|| DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality) { // Ensure no non-digits if we are requesting numbers. This shouldn't be possible unless
// user finds some way to bring up soft keyboard.
if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC
|| mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
if (letters > 0 || symbols > 0) { if (letters > 0 || symbols > 0) {
// This shouldn't be possible unless user finds some way to bring up errorCode |= CONTAIN_NON_DIGITS;
// soft keyboard
return getString(R.string.lockpassword_pin_contains_non_digits);
} }
// Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
final int sequence = LockPatternUtils.maxLengthSequence(password);
if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality
&& sequence > LockPatternUtils.MAX_ALLOWED_SEQUENCE) {
return getString(R.string.lockpassword_pin_no_sequential_digits);
}
} else if (DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality) {
if (letters < mPasswordMinLetters) {
return String.format(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters),
mPasswordMinLetters);
} else if (numbers < mPasswordMinNumeric) {
return String.format(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric),
mPasswordMinNumeric);
} else if (lowercase < mPasswordMinLowerCase) {
return String.format(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase),
mPasswordMinLowerCase);
} else if (uppercase < mPasswordMinUpperCase) {
return String.format(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase),
mPasswordMinUpperCase);
} else if (symbols < mPasswordMinSymbols) {
return String.format(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols),
mPasswordMinSymbols);
} else if (nonletter < mPasswordMinNonLetter) {
return String.format(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter),
mPasswordMinNonLetter);
}
} else {
final boolean alphabetic = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
== mRequestedQuality;
final boolean alphanumeric = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
== mRequestedQuality;
if ((alphabetic || alphanumeric) && letters == 0) {
return getString(R.string.lockpassword_password_requires_alpha);
}
if (alphanumeric && numbers == 0) {
return getString(R.string.lockpassword_password_requires_digit);
}
}
if(mLockPatternUtils.checkPasswordHistory(password, mUserId)) {
return getString(mIsAlphaMode ? R.string.lockpassword_password_recently_used
: R.string.lockpassword_pin_recently_used);
} }
return null; // Check the requirements one by one.
for (int i = 0; i < mPasswordRequirements.length; i++) {
int passwordRestriction = mPasswordRequirements[i];
switch (passwordRestriction) {
case MIN_LETTER_IN_PASSWORD:
if (letters < mPasswordMinLetters) {
errorCode |= NOT_ENOUGH_LETTER;
}
break;
case MIN_UPPER_LETTERS_IN_PASSWORD:
if (uppercase < mPasswordMinUpperCase) {
errorCode |= NOT_ENOUGH_UPPER_CASE;
}
break;
case MIN_LOWER_LETTERS_IN_PASSWORD:
if (lowercase < mPasswordMinLowerCase) {
errorCode |= NOT_ENOUGH_LOWER_CASE;
}
break;
case MIN_SYMBOLS_IN_PASSWORD:
if (symbols < mPasswordMinSymbols) {
errorCode |= NOT_ENOUGH_SYMBOLS;
}
break;
case MIN_NUMBER_IN_PASSWORD:
if (numbers < mPasswordMinNumeric) {
errorCode |= NOT_ENOUGH_DIGITS;
}
break;
case MIN_NON_LETTER_IN_PASSWORD:
if (nonletter < mPasswordMinNonLetter) {
errorCode |= NOT_ENOUGH_NON_LETTER;
}
break;
}
}
return errorCode;
} }
public void handleNext() { public void handleNext() {
@@ -540,10 +679,8 @@ public class ChooseLockPassword extends SettingsActivity {
if (TextUtils.isEmpty(mChosenPassword)) { if (TextUtils.isEmpty(mChosenPassword)) {
return; return;
} }
String errorMsg = null;
if (mUiStage == Stage.Introduction) { if (mUiStage == Stage.Introduction) {
errorMsg = validatePassword(mChosenPassword); if (validatePassword(mChosenPassword) == NO_ERROR) {
if (errorMsg == null) {
mFirstPin = mChosenPassword; mFirstPin = mChosenPassword;
mPasswordEntry.setText(""); mPasswordEntry.setText("");
updateStage(Stage.NeedToConfirm); updateStage(Stage.NeedToConfirm);
@@ -559,9 +696,6 @@ public class ChooseLockPassword extends SettingsActivity {
updateStage(Stage.ConfirmWrong); updateStage(Stage.ConfirmWrong);
} }
} }
if (errorMsg != null) {
showError(errorMsg, mUiStage);
}
} }
protected void setNextEnabled(boolean enabled) { protected void setNextEnabled(boolean enabled) {
@@ -584,14 +718,6 @@ public class ChooseLockPassword extends SettingsActivity {
} }
} }
private void showError(String msg, final Stage next) {
mHeaderText.setText(msg);
mHeaderText.announceForAccessibility(mHeaderText.getText());
Message mesg = mHandler.obtainMessage(MSG_SHOW_ERROR, next);
mHandler.removeMessages(MSG_SHOW_ERROR);
mHandler.sendMessageDelayed(mesg, ERROR_MESSAGE_TIMEOUT);
}
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
// Check if this was the result of hitting the enter or "done" key // Check if this was the result of hitting the enter or "done" key
if (actionId == EditorInfo.IME_NULL if (actionId == EditorInfo.IME_NULL
@@ -603,6 +729,68 @@ public class ChooseLockPassword extends SettingsActivity {
return false; return false;
} }
/**
* @param errorCode error code returned from {@link #validatePassword(String)}.
* @return an array of messages describing the error, important messages come first.
*/
private String[] convertErrorCodeToMessages(int errorCode) {
List<String> messages = new ArrayList<>();
if ((errorCode & CONTAIN_INVALID_CHARACTERS) > 0) {
messages.add(getString(R.string.lockpassword_illegal_character));
}
if ((errorCode & CONTAIN_NON_DIGITS) > 0) {
messages.add(getString(R.string.lockpassword_pin_contains_non_digits));
}
if ((errorCode & NOT_ENOUGH_LETTER) > 0) {
messages.add(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
mPasswordMinLetters));
}
if ((errorCode & NOT_ENOUGH_UPPER_CASE) > 0) {
messages.add(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase,
mPasswordMinUpperCase));
}
if ((errorCode & NOT_ENOUGH_LOWER_CASE) > 0) {
messages.add(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase,
mPasswordMinLowerCase));
}
if ((errorCode & NOT_ENOUGH_DIGITS) > 0) {
messages.add(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric,
mPasswordMinNumeric));
}
if ((errorCode & NOT_ENOUGH_SYMBOLS) > 0) {
messages.add(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols,
mPasswordMinSymbols));
}
if ((errorCode & NOT_ENOUGH_NON_LETTER) > 0) {
messages.add(getResources().getQuantityString(
R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter,
mPasswordMinNonLetter));
}
if ((errorCode & TOO_SHORT) > 0) {
messages.add(getString(mIsAlphaMode ?
R.string.lockpassword_password_too_short
: R.string.lockpassword_pin_too_short, mPasswordMinLength));
}
if ((errorCode & TOO_LONG) > 0) {
messages.add(getString(mIsAlphaMode ?
R.string.lockpassword_password_too_long
: R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1));
}
if ((errorCode & CONTAIN_SEQUENTIAL_DIGITS) > 0) {
messages.add(getString(R.string.lockpassword_pin_no_sequential_digits));
}
if ((errorCode & RECENTLY_USED) > 0) {
messages.add(getString((mIsAlphaMode) ? R.string.lockpassword_password_recently_used
: R.string.lockpassword_pin_recently_used));
}
return messages.toArray(new String[0]);
}
/** /**
* Update the hint based on current Stage and length of password entry * Update the hint based on current Stage and length of password entry
*/ */
@@ -611,29 +799,33 @@ public class ChooseLockPassword extends SettingsActivity {
String password = mPasswordEntry.getText().toString(); String password = mPasswordEntry.getText().toString();
final int length = password.length(); final int length = password.length();
if (mUiStage == Stage.Introduction) { if (mUiStage == Stage.Introduction) {
if (length < mPasswordMinLength) { mPasswordRestrictionView.setVisibility(View.VISIBLE);
String msg = getString(mIsAlphaMode ? R.string.lockpassword_password_too_short final int errorCode = validatePassword(password);
: R.string.lockpassword_pin_too_short, mPasswordMinLength); String[] messages = convertErrorCodeToMessages(errorCode);
mHeaderText.setText(msg); // Update the fulfillment of requirements.
setNextEnabled(false); mPasswordRequirementAdapter.setRequirements(messages);
} else { // Enable/Disable the next button accordingly.
String error = validatePassword(password); setNextEnabled(errorCode == NO_ERROR);
if (error != null) {
mHeaderText.setText(error);
setNextEnabled(false);
} else {
mHeaderText.setText(null);
setNextEnabled(true);
}
}
} else { } else {
mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint); // Hide password requirement view when we are just asking user to confirm the pw.
mPasswordRestrictionView.setVisibility(View.GONE);
setHeaderText(getString(
mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint));
setNextEnabled(canInput && length > 0); setNextEnabled(canInput && length > 0);
} }
setNextText(mUiStage.buttonText); setNextText(mUiStage.buttonText);
mPasswordEntryInputDisabler.setInputEnabled(canInput); mPasswordEntryInputDisabler.setInputEnabled(canInput);
} }
private void setHeaderText(String text) {
// Only set the text if it is different than the existing one to avoid announcing again.
if (!TextUtils.isEmpty(mHeaderText.getText())
&& mHeaderText.getText().toString().equals(text)) {
return;
}
mHeaderText.setText(text);
}
public void afterTextChanged(Editable s) { public void afterTextChanged(Editable s) {
// Changing the text while error displayed resets to NeedToConfirm state // Changing the text while error displayed resets to NeedToConfirm state
if (mUiStage == Stage.ConfirmWrong) { if (mUiStage == Stage.ConfirmWrong) {

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2016 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.settings.password;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.android.settings.R;
import static com.android.settings.password.PasswordRequirementAdapter
.PasswordRequirementViewHolder;
/**
* Used in {@link com.android.settings.ConfirmLockPassword} to show password requirements.
*/
public class PasswordRequirementAdapter extends
RecyclerView.Adapter<PasswordRequirementViewHolder> {
private String[] mRequirements;
public PasswordRequirementAdapter() {
setHasStableIds(true);
}
@Override
public PasswordRequirementViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.password_requirement_item, parent, false);
return new PasswordRequirementViewHolder(v);
}
@Override
public int getItemCount() {
return mRequirements.length;
}
public void setRequirements(String[] requirements) {
mRequirements = requirements;
notifyDataSetChanged();
}
@Override
public long getItemId(int position) {
return mRequirements[position].hashCode();
}
@Override
public void onBindViewHolder(PasswordRequirementViewHolder holder, int position) {
holder.mDescriptionText.setText(mRequirements[position]);
}
public static class PasswordRequirementViewHolder extends RecyclerView.ViewHolder {
private TextView mDescriptionText;
public PasswordRequirementViewHolder(View itemView) {
super(itemView);
mDescriptionText = (TextView) itemView;
}
}
}