Home | History | Annotate | Download | only in settings
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings;
     18 
     19 import android.app.Activity;
     20 import android.app.Fragment;
     21 import android.app.admin.DevicePolicyManager;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.graphics.drawable.InsetDrawable;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.os.Message;
     28 import android.support.v7.widget.LinearLayoutManager;
     29 import android.support.v7.widget.RecyclerView;
     30 import android.text.Editable;
     31 import android.text.InputType;
     32 import android.text.Selection;
     33 import android.text.Spannable;
     34 import android.text.TextUtils;
     35 import android.text.TextWatcher;
     36 import android.util.Log;
     37 import android.view.inputmethod.EditorInfo;
     38 import android.view.KeyEvent;
     39 import android.view.LayoutInflater;
     40 import android.view.View;
     41 import android.view.View.OnClickListener;
     42 import android.view.ViewGroup;
     43 import android.widget.Button;
     44 import android.widget.EditText;
     45 import android.widget.LinearLayout;
     46 import android.widget.TextView;
     47 import android.widget.TextView.OnEditorActionListener;
     48 
     49 import com.android.internal.logging.MetricsProto.MetricsEvent;
     50 import com.android.internal.widget.LockPatternUtils;
     51 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
     52 import com.android.internal.widget.TextViewInputDisabler;
     53 import com.android.settings.notification.RedactionInterstitial;
     54 import com.android.settings.password.PasswordRequirementAdapter;
     55 import com.android.setupwizardlib.GlifLayout;
     56 
     57 import java.util.ArrayList;
     58 import java.util.List;
     59 
     60 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
     61 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
     62 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
     63 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
     64 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
     65 
     66 public class ChooseLockPassword extends SettingsActivity {
     67     public static final String PASSWORD_MIN_KEY = "lockscreen.password_min";
     68     public static final String PASSWORD_MAX_KEY = "lockscreen.password_max";
     69     public static final String PASSWORD_MIN_LETTERS_KEY = "lockscreen.password_min_letters";
     70     public static final String PASSWORD_MIN_LOWERCASE_KEY = "lockscreen.password_min_lowercase";
     71     public static final String PASSWORD_MIN_UPPERCASE_KEY = "lockscreen.password_min_uppercase";
     72     public static final String PASSWORD_MIN_NUMERIC_KEY = "lockscreen.password_min_numeric";
     73     public static final String PASSWORD_MIN_SYMBOLS_KEY = "lockscreen.password_min_symbols";
     74     public static final String PASSWORD_MIN_NONLETTER_KEY = "lockscreen.password_min_nonletter";
     75 
     76     private static final String TAG = "ChooseLockPassword";
     77 
     78     @Override
     79     public Intent getIntent() {
     80         Intent modIntent = new Intent(super.getIntent());
     81         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
     82         return modIntent;
     83     }
     84 
     85     public static Intent createIntent(Context context, int quality,
     86             int minLength, final int maxLength, boolean requirePasswordToDecrypt,
     87             boolean confirmCredentials) {
     88         Intent intent = new Intent().setClass(context, ChooseLockPassword.class);
     89         intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality);
     90         intent.putExtra(PASSWORD_MIN_KEY, minLength);
     91         intent.putExtra(PASSWORD_MAX_KEY, maxLength);
     92         intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials);
     93         intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePasswordToDecrypt);
     94         return intent;
     95     }
     96 
     97     public static Intent createIntent(Context context, int quality,
     98             int minLength, final int maxLength, boolean requirePasswordToDecrypt,
     99             boolean confirmCredentials, int userId) {
    100         Intent intent = createIntent(context, quality, minLength, maxLength,
    101                 requirePasswordToDecrypt, confirmCredentials);
    102         intent.putExtra(Intent.EXTRA_USER_ID, userId);
    103         return intent;
    104     }
    105 
    106     public static Intent createIntent(Context context, int quality,
    107             int minLength, final int maxLength, boolean requirePasswordToDecrypt, String password) {
    108         Intent intent = createIntent(context, quality, minLength, maxLength,
    109                 requirePasswordToDecrypt, false);
    110         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, password);
    111         return intent;
    112     }
    113 
    114     public static Intent createIntent(Context context, int quality, int minLength,
    115             int maxLength, boolean requirePasswordToDecrypt, String password, int userId) {
    116         Intent intent = createIntent(context, quality, minLength, maxLength,
    117                 requirePasswordToDecrypt, password);
    118         intent.putExtra(Intent.EXTRA_USER_ID, userId);
    119         return intent;
    120     }
    121 
    122     public static Intent createIntent(Context context, int quality,
    123             int minLength, final int maxLength, boolean requirePasswordToDecrypt, long challenge) {
    124         Intent intent = createIntent(context, quality, minLength, maxLength,
    125                 requirePasswordToDecrypt, false);
    126         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true);
    127         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge);
    128         return intent;
    129     }
    130 
    131     public static Intent createIntent(Context context, int quality, int minLength,
    132             int maxLength, boolean requirePasswordToDecrypt, long challenge, int userId) {
    133         Intent intent = createIntent(context, quality, minLength, maxLength,
    134                 requirePasswordToDecrypt, challenge);
    135         intent.putExtra(Intent.EXTRA_USER_ID, userId);
    136         return intent;
    137     }
    138 
    139     @Override
    140     protected boolean isValidFragment(String fragmentName) {
    141         if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true;
    142         return false;
    143     }
    144 
    145     /* package */ Class<? extends Fragment> getFragmentClass() {
    146         return ChooseLockPasswordFragment.class;
    147     }
    148 
    149     @Override
    150     protected void onCreate(Bundle savedInstanceState) {
    151         super.onCreate(savedInstanceState);
    152         CharSequence msg = getText(R.string.lockpassword_choose_your_password_header);
    153         setTitle(msg);
    154         LinearLayout layout = (LinearLayout) findViewById(R.id.content_parent);
    155         layout.setFitsSystemWindows(false);
    156     }
    157 
    158     public static class ChooseLockPasswordFragment extends InstrumentedFragment
    159             implements OnClickListener, OnEditorActionListener, TextWatcher,
    160             SaveAndFinishWorker.Listener {
    161         private static final String KEY_FIRST_PIN = "first_pin";
    162         private static final String KEY_UI_STAGE = "ui_stage";
    163         private static final String KEY_CURRENT_PASSWORD = "current_password";
    164         private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
    165 
    166         private String mCurrentPassword;
    167         private String mChosenPassword;
    168         private boolean mHasChallenge;
    169         private long mChallenge;
    170         private EditText mPasswordEntry;
    171         private TextViewInputDisabler mPasswordEntryInputDisabler;
    172         private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
    173         private int mPasswordMaxLength = 16;
    174         private int mPasswordMinLetters = 0;
    175         private int mPasswordMinUpperCase = 0;
    176         private int mPasswordMinLowerCase = 0;
    177         private int mPasswordMinSymbols = 0;
    178         private int mPasswordMinNumeric = 0;
    179         private int mPasswordMinNonLetter = 0;
    180         private int mPasswordMinLengthToFulfillAllPolicies = 0;
    181         private int mUserId;
    182         private boolean mHideDrawer = false;
    183         /**
    184          * Password requirements that we need to verify.
    185          */
    186         private int[] mPasswordRequirements;
    187 
    188         private LockPatternUtils mLockPatternUtils;
    189         private SaveAndFinishWorker mSaveAndFinishWorker;
    190         private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
    191         private ChooseLockSettingsHelper mChooseLockSettingsHelper;
    192         private Stage mUiStage = Stage.Introduction;
    193         private PasswordRequirementAdapter mPasswordRequirementAdapter;
    194 
    195         private TextView mHeaderText;
    196         private String mFirstPin;
    197         private RecyclerView mPasswordRestrictionView;
    198         private boolean mIsAlphaMode;
    199         private Button mCancelButton;
    200         private Button mNextButton;
    201 
    202         private TextChangedHandler mTextChangedHandler;
    203 
    204         private static final int CONFIRM_EXISTING_REQUEST = 58;
    205         static final int RESULT_FINISHED = RESULT_FIRST_USER;
    206 
    207         private static final int MIN_LETTER_IN_PASSWORD = 0;
    208         private static final int MIN_UPPER_LETTERS_IN_PASSWORD = 1;
    209         private static final int MIN_LOWER_LETTERS_IN_PASSWORD = 2;
    210         private static final int MIN_SYMBOLS_IN_PASSWORD = 3;
    211         private static final int MIN_NUMBER_IN_PASSWORD = 4;
    212         private static final int MIN_NON_LETTER_IN_PASSWORD = 5;
    213 
    214         // Error code returned from {@link #validatePassword(String)}.
    215         private static final int NO_ERROR = 0;
    216         private static final int CONTAIN_INVALID_CHARACTERS = 1 << 0;
    217         private static final int TOO_SHORT = 1 << 1;
    218         private static final int TOO_LONG = 1 << 2;
    219         private static final int CONTAIN_NON_DIGITS = 1 << 3;
    220         private static final int CONTAIN_SEQUENTIAL_DIGITS = 1 << 4;
    221         private static final int RECENTLY_USED = 1 << 5;
    222         private static final int NOT_ENOUGH_LETTER = 1 << 6;
    223         private static final int NOT_ENOUGH_UPPER_CASE = 1 << 7;
    224         private static final int NOT_ENOUGH_LOWER_CASE = 1 << 8;
    225         private static final int NOT_ENOUGH_DIGITS = 1 << 9;
    226         private static final int NOT_ENOUGH_SYMBOLS = 1 << 10;
    227         private static final int NOT_ENOUGH_NON_LETTER = 1 << 11;
    228 
    229         /**
    230          * Keep track internally of where the user is in choosing a pattern.
    231          */
    232         protected enum Stage {
    233 
    234             Introduction(R.string.lockpassword_choose_your_password_header,
    235                     R.string.lockpassword_choose_your_pin_header,
    236                     R.string.lockpassword_continue_label),
    237 
    238             NeedToConfirm(R.string.lockpassword_confirm_your_password_header,
    239                     R.string.lockpassword_confirm_your_pin_header,
    240                     R.string.lockpassword_ok_label),
    241 
    242             ConfirmWrong(R.string.lockpassword_confirm_passwords_dont_match,
    243                     R.string.lockpassword_confirm_pins_dont_match,
    244                     R.string.lockpassword_continue_label);
    245 
    246             Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) {
    247                 this.alphaHint = hintInAlpha;
    248                 this.numericHint = hintInNumeric;
    249                 this.buttonText = nextButtonText;
    250             }
    251 
    252             public final int alphaHint;
    253             public final int numericHint;
    254             public final int buttonText;
    255         }
    256 
    257         // required constructor for fragments
    258         public ChooseLockPasswordFragment() {
    259 
    260         }
    261 
    262         @Override
    263         public void onCreate(Bundle savedInstanceState) {
    264             super.onCreate(savedInstanceState);
    265             mLockPatternUtils = new LockPatternUtils(getActivity());
    266             Intent intent = getActivity().getIntent();
    267             if (!(getActivity() instanceof ChooseLockPassword)) {
    268                 throw new SecurityException("Fragment contained in wrong activity");
    269             }
    270             // Only take this argument into account if it belongs to the current profile.
    271             mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
    272             processPasswordRequirements(intent);
    273             mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
    274             mHideDrawer = getActivity().getIntent().getBooleanExtra(EXTRA_HIDE_DRAWER, false);
    275 
    276             if (intent.getBooleanExtra(
    277                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) {
    278                 SaveAndFinishWorker w = new SaveAndFinishWorker();
    279                 final boolean required = getActivity().getIntent().getBooleanExtra(
    280                         EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
    281                 String current = intent.getStringExtra(
    282                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
    283                 w.setBlocking(true);
    284                 w.setListener(this);
    285                 w.start(mChooseLockSettingsHelper.utils(), required,
    286                         false, 0, current, current, mRequestedQuality, mUserId);
    287             }
    288             mTextChangedHandler = new TextChangedHandler();
    289         }
    290 
    291         @Override
    292         public View onCreateView(LayoutInflater inflater, ViewGroup container,
    293                 Bundle savedInstanceState) {
    294             return inflater.inflate(R.layout.choose_lock_password, container, false);
    295         }
    296 
    297         @Override
    298         public void onViewCreated(View view, Bundle savedInstanceState) {
    299             super.onViewCreated(view, savedInstanceState);
    300 
    301             mCancelButton = (Button) view.findViewById(R.id.cancel_button);
    302             mCancelButton.setOnClickListener(this);
    303             mNextButton = (Button) view.findViewById(R.id.next_button);
    304             mNextButton.setOnClickListener(this);
    305 
    306             mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
    307                     || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality
    308                     || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality;
    309 
    310             setupPasswordRequirementsView(view);
    311 
    312             mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
    313             mPasswordEntry = (EditText) view.findViewById(R.id.password_entry);
    314             mPasswordEntry.setOnEditorActionListener(this);
    315             mPasswordEntry.addTextChangedListener(this);
    316             mPasswordEntry.requestFocus();
    317             mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
    318 
    319             final Activity activity = getActivity();
    320             mHeaderText = (TextView) view.findViewById(R.id.headerText);
    321 
    322             int currentType = mPasswordEntry.getInputType();
    323             mPasswordEntry.setInputType(mIsAlphaMode ? currentType
    324                     : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
    325 
    326             Intent intent = getActivity().getIntent();
    327             final boolean confirmCredentials = intent.getBooleanExtra(
    328                     ChooseLockGeneric.CONFIRM_CREDENTIALS, true);
    329             mCurrentPassword = intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
    330             mHasChallenge = intent.getBooleanExtra(
    331                     ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
    332             mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
    333             if (savedInstanceState == null) {
    334                 updateStage(Stage.Introduction);
    335                 if (confirmCredentials) {
    336                     mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST,
    337                             getString(R.string.unlock_set_unlock_launch_picker_title), true,
    338                             mUserId);
    339                 }
    340             } else {
    341                 // restore from previous state
    342                 mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN);
    343                 final String state = savedInstanceState.getString(KEY_UI_STAGE);
    344                 if (state != null) {
    345                     mUiStage = Stage.valueOf(state);
    346                     updateStage(mUiStage);
    347                 }
    348 
    349                 if (mCurrentPassword == null) {
    350                     mCurrentPassword = savedInstanceState.getString(KEY_CURRENT_PASSWORD);
    351                 }
    352 
    353                 // Re-attach to the exiting worker if there is one.
    354                 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
    355                         FRAGMENT_TAG_SAVE_AND_FINISH);
    356             }
    357 
    358             // Workaround to show one password requirement below EditText when IME is shown.
    359             // By adding an inset to the edit text background, we make the EditText occupy more
    360             // vertical space, and the keyboard will then avoid hiding it. We have also set
    361             // negative margin in the layout below in order to have them show in the correct
    362             // position.
    363             final int visibleVerticalSpaceBelowPassword =
    364                     getResources().getDimensionPixelOffset(
    365                         R.dimen.visible_vertical_space_below_password);
    366             InsetDrawable drawable =
    367                     new InsetDrawable(
    368                     mPasswordEntry.getBackground(), 0, 0, 0, visibleVerticalSpaceBelowPassword);
    369             mPasswordEntry.setBackgroundDrawable(drawable);
    370             LinearLayout bottomContainer = (LinearLayout) view.findViewById(R.id.bottom_container);
    371             LinearLayout.LayoutParams bottomContainerLp =
    372                     (LinearLayout.LayoutParams) bottomContainer.getLayoutParams();
    373             bottomContainerLp.setMargins(0, -visibleVerticalSpaceBelowPassword, 0, 0);
    374 
    375             if (activity instanceof SettingsActivity) {
    376                 final SettingsActivity sa = (SettingsActivity) activity;
    377                 int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header
    378                         : R.string.lockpassword_choose_your_pin_header;
    379                 CharSequence title = getText(id);
    380                 sa.setTitle(title);
    381                 ((GlifLayout) view).setHeaderText(title);
    382             }
    383         }
    384 
    385         private void setupPasswordRequirementsView(View view) {
    386             // Construct passwordRequirements and requirementDescriptions.
    387             List<Integer> passwordRequirements = new ArrayList<>();
    388             List<String> requirementDescriptions = new ArrayList<>();
    389             if (mPasswordMinUpperCase > 0) {
    390                 passwordRequirements.add(MIN_UPPER_LETTERS_IN_PASSWORD);
    391                 requirementDescriptions.add(getResources().getQuantityString(
    392                         R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase,
    393                         mPasswordMinUpperCase));
    394             }
    395             if (mPasswordMinLowerCase > 0) {
    396                 passwordRequirements.add(MIN_LOWER_LETTERS_IN_PASSWORD);
    397                 requirementDescriptions.add(getResources().getQuantityString(
    398                         R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase,
    399                         mPasswordMinLowerCase));
    400             }
    401             if (mPasswordMinLetters > 0) {
    402                 if (mPasswordMinLetters > mPasswordMinUpperCase + mPasswordMinLowerCase) {
    403                     passwordRequirements.add(MIN_LETTER_IN_PASSWORD);
    404                     requirementDescriptions.add(getResources().getQuantityString(
    405                             R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
    406                             mPasswordMinLetters));
    407                 }
    408             }
    409             if (mPasswordMinNumeric > 0) {
    410                 passwordRequirements.add(MIN_NUMBER_IN_PASSWORD);
    411                 requirementDescriptions.add(getResources().getQuantityString(
    412                         R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric,
    413                         mPasswordMinNumeric));
    414             }
    415             if (mPasswordMinSymbols > 0) {
    416                 passwordRequirements.add(MIN_SYMBOLS_IN_PASSWORD);
    417                 requirementDescriptions.add(getResources().getQuantityString(
    418                         R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols,
    419                         mPasswordMinSymbols));
    420             }
    421             if (mPasswordMinNonLetter > 0) {
    422                 if (mPasswordMinNonLetter > mPasswordMinNumeric + mPasswordMinSymbols) {
    423                     passwordRequirements.add(MIN_NON_LETTER_IN_PASSWORD);
    424                     requirementDescriptions.add(getResources().getQuantityString(
    425                             R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter,
    426 
    427                             mPasswordMinNonLetter));
    428                 }
    429             }
    430             // Convert list to array.
    431             mPasswordRequirements = passwordRequirements.stream().mapToInt(i -> i).toArray();
    432             mPasswordRestrictionView =
    433                     (RecyclerView) view.findViewById(R.id.password_requirements_view);
    434             mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity()));
    435             mPasswordRequirementAdapter = new PasswordRequirementAdapter();
    436             mPasswordRestrictionView.setAdapter(mPasswordRequirementAdapter);
    437         }
    438 
    439         @Override
    440         protected int getMetricsCategory() {
    441             return MetricsEvent.CHOOSE_LOCK_PASSWORD;
    442         }
    443 
    444         @Override
    445         public void onResume() {
    446             super.onResume();
    447             updateStage(mUiStage);
    448             if (mSaveAndFinishWorker != null) {
    449                 mSaveAndFinishWorker.setListener(this);
    450             } else {
    451                 mPasswordEntry.requestFocus();
    452             }
    453         }
    454 
    455         @Override
    456         public void onPause() {
    457             if (mSaveAndFinishWorker != null) {
    458                 mSaveAndFinishWorker.setListener(null);
    459             }
    460             super.onPause();
    461         }
    462 
    463         @Override
    464         public void onSaveInstanceState(Bundle outState) {
    465             super.onSaveInstanceState(outState);
    466             outState.putString(KEY_UI_STAGE, mUiStage.name());
    467             outState.putString(KEY_FIRST_PIN, mFirstPin);
    468             outState.putString(KEY_CURRENT_PASSWORD, mCurrentPassword);
    469         }
    470 
    471         @Override
    472         public void onActivityResult(int requestCode, int resultCode,
    473                 Intent data) {
    474             super.onActivityResult(requestCode, resultCode, data);
    475             switch (requestCode) {
    476                 case CONFIRM_EXISTING_REQUEST:
    477                     if (resultCode != Activity.RESULT_OK) {
    478                         getActivity().setResult(RESULT_FINISHED);
    479                         getActivity().finish();
    480                     } else {
    481                         mCurrentPassword = data.getStringExtra(
    482                                 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
    483                     }
    484                     break;
    485             }
    486         }
    487 
    488         protected Intent getRedactionInterstitialIntent(Context context) {
    489             return RedactionInterstitial.createStartIntent(context, mUserId);
    490         }
    491 
    492         protected void updateStage(Stage stage) {
    493             final Stage previousStage = mUiStage;
    494             mUiStage = stage;
    495             updateUi();
    496 
    497             // If the stage changed, announce the header for accessibility. This
    498             // is a no-op when accessibility is disabled.
    499             if (previousStage != stage) {
    500                 mHeaderText.announceForAccessibility(mHeaderText.getText());
    501             }
    502         }
    503 
    504         /**
    505          * Read the requirements from {@link DevicePolicyManager} and intent and aggregate them.
    506          *
    507          * @param intent the incoming intent
    508          */
    509         private void processPasswordRequirements(Intent intent) {
    510             final int dpmPasswordQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId);
    511             mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
    512                     mRequestedQuality), dpmPasswordQuality);
    513             mPasswordMinLength = Math.max(Math.max(
    514                     LockPatternUtils.MIN_LOCK_PASSWORD_SIZE,
    515                     intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)),
    516                     mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId));
    517             mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength);
    518             mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY,
    519                     mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters(
    520                     mUserId));
    521             mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY,
    522                     mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase(
    523                     mUserId));
    524             mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY,
    525                     mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase(
    526                     mUserId));
    527             mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY,
    528                     mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric(
    529                     mUserId));
    530             mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY,
    531                     mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols(
    532                     mUserId));
    533             mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY,
    534                     mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter(
    535                     mUserId));
    536 
    537             // Modify the value based on dpm policy.
    538             switch (dpmPasswordQuality) {
    539                 case PASSWORD_QUALITY_ALPHABETIC:
    540                     if (mPasswordMinLetters == 0) {
    541                         mPasswordMinLetters = 1;
    542                     }
    543                     break;
    544                 case PASSWORD_QUALITY_ALPHANUMERIC:
    545                     if (mPasswordMinLetters == 0) {
    546                         mPasswordMinLetters = 1;
    547                     }
    548                     if (mPasswordMinNumeric == 0) {
    549                         mPasswordMinNumeric = 1;
    550                     }
    551                     break;
    552                 case PASSWORD_QUALITY_COMPLEX:
    553                     // Reserve all the requirements.
    554                     break;
    555                 default:
    556                     mPasswordMinNumeric = 0;
    557                     mPasswordMinLetters = 0;
    558                     mPasswordMinUpperCase = 0;
    559                     mPasswordMinLowerCase = 0;
    560                     mPasswordMinSymbols = 0;
    561                     mPasswordMinNonLetter = 0;
    562             }
    563             mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies();
    564         }
    565 
    566         /**
    567          * Validates PIN and returns the validation result.
    568          *
    569          * @param password the raw password the user typed in
    570          * @return the validation result.
    571          */
    572         private int validatePassword(String password) {
    573             int errorCode = NO_ERROR;
    574 
    575             if (password.length() < mPasswordMinLength) {
    576                 if (mPasswordMinLength > mPasswordMinLengthToFulfillAllPolicies) {
    577                     errorCode |= TOO_SHORT;
    578                 }
    579             } else if (password.length() > mPasswordMaxLength) {
    580                 errorCode |= TOO_LONG;
    581             } else {
    582                 // The length requirements are fulfilled.
    583                 if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
    584                     // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
    585                     final int sequence = LockPatternUtils.maxLengthSequence(password);
    586                     if (sequence > LockPatternUtils.MAX_ALLOWED_SEQUENCE) {
    587                         errorCode |= CONTAIN_SEQUENTIAL_DIGITS;
    588                     }
    589                 }
    590                 // Is the password recently used?
    591                 if (mLockPatternUtils.checkPasswordHistory(password, mUserId)) {
    592                     errorCode |= RECENTLY_USED;
    593                 }
    594             }
    595 
    596             // Count different types of character.
    597             int letters = 0;
    598             int numbers = 0;
    599             int lowercase = 0;
    600             int symbols = 0;
    601             int uppercase = 0;
    602             int nonletter = 0;
    603             for (int i = 0; i < password.length(); i++) {
    604                 char c = password.charAt(i);
    605                 // allow non control Latin-1 characters only
    606                 if (c < 32 || c > 127) {
    607                     errorCode |= CONTAIN_INVALID_CHARACTERS;
    608                     continue;
    609                 }
    610                 if (c >= '0' && c <= '9') {
    611                     numbers++;
    612                     nonletter++;
    613                 } else if (c >= 'A' && c <= 'Z') {
    614                     letters++;
    615                     uppercase++;
    616                 } else if (c >= 'a' && c <= 'z') {
    617                     letters++;
    618                     lowercase++;
    619                 } else {
    620                     symbols++;
    621                     nonletter++;
    622                 }
    623             }
    624 
    625             // Ensure no non-digits if we are requesting numbers. This shouldn't be possible unless
    626             // user finds some way to bring up soft keyboard.
    627             if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC
    628                     || mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
    629                 if (letters > 0 || symbols > 0) {
    630                     errorCode |= CONTAIN_NON_DIGITS;
    631                 }
    632             }
    633 
    634             // Check the requirements one by one.
    635             for (int i = 0; i < mPasswordRequirements.length; i++) {
    636                 int passwordRestriction = mPasswordRequirements[i];
    637                 switch (passwordRestriction) {
    638                     case MIN_LETTER_IN_PASSWORD:
    639                         if (letters < mPasswordMinLetters) {
    640                             errorCode |= NOT_ENOUGH_LETTER;
    641                         }
    642                         break;
    643                     case MIN_UPPER_LETTERS_IN_PASSWORD:
    644                         if (uppercase < mPasswordMinUpperCase) {
    645                             errorCode |= NOT_ENOUGH_UPPER_CASE;
    646                         }
    647                         break;
    648                     case MIN_LOWER_LETTERS_IN_PASSWORD:
    649                         if (lowercase < mPasswordMinLowerCase) {
    650                             errorCode |= NOT_ENOUGH_LOWER_CASE;
    651                         }
    652                         break;
    653                     case MIN_SYMBOLS_IN_PASSWORD:
    654                         if (symbols < mPasswordMinSymbols) {
    655                             errorCode |= NOT_ENOUGH_SYMBOLS;
    656                         }
    657                         break;
    658                     case MIN_NUMBER_IN_PASSWORD:
    659                         if (numbers < mPasswordMinNumeric) {
    660                             errorCode |= NOT_ENOUGH_DIGITS;
    661                         }
    662                         break;
    663                     case MIN_NON_LETTER_IN_PASSWORD:
    664                         if (nonletter < mPasswordMinNonLetter) {
    665                             errorCode |= NOT_ENOUGH_NON_LETTER;
    666                         }
    667                         break;
    668                 }
    669             }
    670             return errorCode;
    671         }
    672 
    673         public void handleNext() {
    674             if (mSaveAndFinishWorker != null) return;
    675             mChosenPassword = mPasswordEntry.getText().toString();
    676             if (TextUtils.isEmpty(mChosenPassword)) {
    677                 return;
    678             }
    679             if (mUiStage == Stage.Introduction) {
    680                 if (validatePassword(mChosenPassword) == NO_ERROR) {
    681                     mFirstPin = mChosenPassword;
    682                     mPasswordEntry.setText("");
    683                     updateStage(Stage.NeedToConfirm);
    684                 }
    685             } else if (mUiStage == Stage.NeedToConfirm) {
    686                 if (mFirstPin.equals(mChosenPassword)) {
    687                     startSaveAndFinish();
    688                 } else {
    689                     CharSequence tmp = mPasswordEntry.getText();
    690                     if (tmp != null) {
    691                         Selection.setSelection((Spannable) tmp, 0, tmp.length());
    692                     }
    693                     updateStage(Stage.ConfirmWrong);
    694                 }
    695             }
    696         }
    697 
    698         protected void setNextEnabled(boolean enabled) {
    699             mNextButton.setEnabled(enabled);
    700         }
    701 
    702         protected void setNextText(int text) {
    703             mNextButton.setText(text);
    704         }
    705 
    706         public void onClick(View v) {
    707             switch (v.getId()) {
    708                 case R.id.next_button:
    709                     handleNext();
    710                     break;
    711 
    712                 case R.id.cancel_button:
    713                     getActivity().finish();
    714                     break;
    715             }
    716         }
    717 
    718         public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    719             // Check if this was the result of hitting the enter or "done" key
    720             if (actionId == EditorInfo.IME_NULL
    721                     || actionId == EditorInfo.IME_ACTION_DONE
    722                     || actionId == EditorInfo.IME_ACTION_NEXT) {
    723                 handleNext();
    724                 return true;
    725             }
    726             return false;
    727         }
    728 
    729         /**
    730          * @param errorCode error code returned from {@link #validatePassword(String)}.
    731          * @return an array of messages describing the error, important messages come first.
    732          */
    733         private String[] convertErrorCodeToMessages(int errorCode) {
    734             List<String> messages = new ArrayList<>();
    735             if ((errorCode & CONTAIN_INVALID_CHARACTERS) > 0) {
    736                 messages.add(getString(R.string.lockpassword_illegal_character));
    737             }
    738             if ((errorCode & CONTAIN_NON_DIGITS) > 0) {
    739                 messages.add(getString(R.string.lockpassword_pin_contains_non_digits));
    740             }
    741             if ((errorCode & NOT_ENOUGH_UPPER_CASE) > 0) {
    742                 messages.add(getResources().getQuantityString(
    743                         R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase,
    744                         mPasswordMinUpperCase));
    745             }
    746             if ((errorCode & NOT_ENOUGH_LOWER_CASE) > 0) {
    747                 messages.add(getResources().getQuantityString(
    748                         R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase,
    749                         mPasswordMinLowerCase));
    750             }
    751             if ((errorCode & NOT_ENOUGH_LETTER) > 0) {
    752                 messages.add(getResources().getQuantityString(
    753                         R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
    754                         mPasswordMinLetters));
    755             }
    756             if ((errorCode & NOT_ENOUGH_DIGITS) > 0) {
    757                 messages.add(getResources().getQuantityString(
    758                         R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric,
    759                         mPasswordMinNumeric));
    760             }
    761             if ((errorCode & NOT_ENOUGH_SYMBOLS) > 0) {
    762                 messages.add(getResources().getQuantityString(
    763                         R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols,
    764                         mPasswordMinSymbols));
    765             }
    766             if ((errorCode & NOT_ENOUGH_NON_LETTER) > 0) {
    767                 messages.add(getResources().getQuantityString(
    768                         R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter,
    769                         mPasswordMinNonLetter));
    770             }
    771             if ((errorCode & TOO_SHORT) > 0) {
    772                 messages.add(getString(mIsAlphaMode ?
    773                         R.string.lockpassword_password_too_short
    774                         : R.string.lockpassword_pin_too_short, mPasswordMinLength));
    775             }
    776             if ((errorCode & TOO_LONG) > 0) {
    777                 messages.add(getString(mIsAlphaMode ?
    778                         R.string.lockpassword_password_too_long
    779                         : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1));
    780             }
    781             if ((errorCode & CONTAIN_SEQUENTIAL_DIGITS) > 0) {
    782                 messages.add(getString(R.string.lockpassword_pin_no_sequential_digits));
    783             }
    784             if ((errorCode & RECENTLY_USED) > 0) {
    785                 messages.add(getString((mIsAlphaMode) ? R.string.lockpassword_password_recently_used
    786                         : R.string.lockpassword_pin_recently_used));
    787             }
    788             return messages.toArray(new String[0]);
    789         }
    790 
    791         private int getMinLengthToFulfillAllPolicies() {
    792             final int minLengthForLetters = Math.max(mPasswordMinLetters,
    793                     mPasswordMinUpperCase + mPasswordMinLowerCase);
    794             final int minLengthForNonLetters = Math.max(mPasswordMinNonLetter,
    795                     mPasswordMinSymbols + mPasswordMinNumeric);
    796             return minLengthForLetters + minLengthForNonLetters;
    797         }
    798 
    799         /**
    800          * Update the hint based on current Stage and length of password entry
    801          */
    802         private void updateUi() {
    803             final boolean canInput = mSaveAndFinishWorker == null;
    804             String password = mPasswordEntry.getText().toString();
    805             final int length = password.length();
    806             if (mUiStage == Stage.Introduction) {
    807                 mPasswordRestrictionView.setVisibility(View.VISIBLE);
    808                 final int errorCode = validatePassword(password);
    809                 String[] messages = convertErrorCodeToMessages(errorCode);
    810                 // Update the fulfillment of requirements.
    811                 mPasswordRequirementAdapter.setRequirements(messages);
    812                 // Enable/Disable the next button accordingly.
    813                 setNextEnabled(errorCode == NO_ERROR);
    814             } else {
    815                 // Hide password requirement view when we are just asking user to confirm the pw.
    816                 mPasswordRestrictionView.setVisibility(View.GONE);
    817                 setHeaderText(getString(
    818                         mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint));
    819                 setNextEnabled(canInput && length > 0);
    820             }
    821             setNextText(mUiStage.buttonText);
    822             mPasswordEntryInputDisabler.setInputEnabled(canInput);
    823         }
    824 
    825         private void setHeaderText(String text) {
    826             // Only set the text if it is different than the existing one to avoid announcing again.
    827             if (!TextUtils.isEmpty(mHeaderText.getText())
    828                     && mHeaderText.getText().toString().equals(text)) {
    829                 return;
    830             }
    831             mHeaderText.setText(text);
    832         }
    833 
    834         public void afterTextChanged(Editable s) {
    835             // Changing the text while error displayed resets to NeedToConfirm state
    836             if (mUiStage == Stage.ConfirmWrong) {
    837                 mUiStage = Stage.NeedToConfirm;
    838             }
    839             // Schedule the UI update.
    840             mTextChangedHandler.notifyAfterTextChanged();
    841         }
    842 
    843         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    844 
    845         }
    846 
    847         public void onTextChanged(CharSequence s, int start, int before, int count) {
    848 
    849         }
    850 
    851         private void startSaveAndFinish() {
    852             if (mSaveAndFinishWorker != null) {
    853                 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
    854                 return;
    855             }
    856 
    857             mPasswordEntryInputDisabler.setInputEnabled(false);
    858             setNextEnabled(false);
    859 
    860             mSaveAndFinishWorker = new SaveAndFinishWorker();
    861             mSaveAndFinishWorker.setListener(this);
    862 
    863             getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
    864                     FRAGMENT_TAG_SAVE_AND_FINISH).commit();
    865             getFragmentManager().executePendingTransactions();
    866 
    867             final boolean required = getActivity().getIntent().getBooleanExtra(
    868                     EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
    869             mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge,
    870                     mChosenPassword, mCurrentPassword, mRequestedQuality, mUserId);
    871         }
    872 
    873         @Override
    874         public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
    875             getActivity().setResult(RESULT_FINISHED, resultData);
    876 
    877             if (!wasSecureBefore) {
    878                 Intent intent = getRedactionInterstitialIntent(getActivity());
    879                 if (intent != null) {
    880                     intent.putExtra(EXTRA_HIDE_DRAWER, mHideDrawer);
    881                     startActivity(intent);
    882                 }
    883             }
    884             getActivity().finish();
    885         }
    886 
    887         class TextChangedHandler extends Handler {
    888             private static final int ON_TEXT_CHANGED = 1;
    889             private static final int DELAY_IN_MILLISECOND = 100;
    890 
    891             /**
    892              * With the introduction of delay, we batch processing the text changed event to reduce
    893              * unnecessary UI updates.
    894              */
    895             private void notifyAfterTextChanged() {
    896                 removeMessages(ON_TEXT_CHANGED);
    897                 sendEmptyMessageDelayed(ON_TEXT_CHANGED, DELAY_IN_MILLISECOND);
    898             }
    899 
    900             @Override
    901             public void handleMessage(Message msg) {
    902                 if (msg.what == ON_TEXT_CHANGED) {
    903                     updateUi();
    904                 }
    905             }
    906         }
    907     }
    908 
    909     private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
    910 
    911         private String mChosenPassword;
    912         private String mCurrentPassword;
    913         private int mRequestedQuality;
    914 
    915         public void start(LockPatternUtils utils, boolean required,
    916                 boolean hasChallenge, long challenge,
    917                 String chosenPassword, String currentPassword, int requestedQuality, int userId) {
    918             prepare(utils, required, hasChallenge, challenge, userId);
    919 
    920             mChosenPassword = chosenPassword;
    921             mCurrentPassword = currentPassword;
    922             mRequestedQuality = requestedQuality;
    923             mUserId = userId;
    924 
    925             start();
    926         }
    927 
    928         @Override
    929         protected Intent saveAndVerifyInBackground() {
    930             Intent result = null;
    931             mUtils.saveLockPassword(mChosenPassword, mCurrentPassword, mRequestedQuality,
    932                     mUserId);
    933 
    934             if (mHasChallenge) {
    935                 byte[] token;
    936                 try {
    937                     token = mUtils.verifyPassword(mChosenPassword, mChallenge, mUserId);
    938                 } catch (RequestThrottledException e) {
    939                     token = null;
    940                 }
    941 
    942                 if (token == null) {
    943                     Log.e(TAG, "critical: no token returned for known good password.");
    944                 }
    945 
    946                 result = new Intent();
    947                 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
    948             }
    949 
    950             return result;
    951         }
    952     }
    953 }
    954