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.inputmethodservice.KeyboardView;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.os.Message;
     28 import android.os.UserHandle;
     29 import android.text.Editable;
     30 import android.text.InputType;
     31 import android.text.Selection;
     32 import android.text.Spannable;
     33 import android.text.TextUtils;
     34 import android.text.TextWatcher;
     35 import android.util.Log;
     36 import android.view.KeyEvent;
     37 import android.view.LayoutInflater;
     38 import android.view.View;
     39 import android.view.View.OnClickListener;
     40 import android.view.ViewGroup;
     41 import android.view.inputmethod.EditorInfo;
     42 import android.widget.Button;
     43 import android.widget.TextView;
     44 import android.widget.TextView.OnEditorActionListener;
     45 
     46 import com.android.internal.logging.MetricsProto.MetricsEvent;
     47 import com.android.internal.widget.LockPatternUtils;
     48 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
     49 import com.android.internal.widget.PasswordEntryKeyboardHelper;
     50 import com.android.internal.widget.PasswordEntryKeyboardView;
     51 import com.android.internal.widget.TextViewInputDisabler;
     52 import com.android.settings.notification.RedactionInterstitial;
     53 
     54 public class ChooseLockPassword extends SettingsActivity {
     55     public static final String PASSWORD_MIN_KEY = "lockscreen.password_min";
     56     public static final String PASSWORD_MAX_KEY = "lockscreen.password_max";
     57     public static final String PASSWORD_MIN_LETTERS_KEY = "lockscreen.password_min_letters";
     58     public static final String PASSWORD_MIN_LOWERCASE_KEY = "lockscreen.password_min_lowercase";
     59     public static final String PASSWORD_MIN_UPPERCASE_KEY = "lockscreen.password_min_uppercase";
     60     public static final String PASSWORD_MIN_NUMERIC_KEY = "lockscreen.password_min_numeric";
     61     public static final String PASSWORD_MIN_SYMBOLS_KEY = "lockscreen.password_min_symbols";
     62     public static final String PASSWORD_MIN_NONLETTER_KEY = "lockscreen.password_min_nonletter";
     63 
     64     private static final String TAG = "ChooseLockPassword";
     65 
     66     @Override
     67     public Intent getIntent() {
     68         Intent modIntent = new Intent(super.getIntent());
     69         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
     70         return modIntent;
     71     }
     72 
     73     public static Intent createIntent(Context context, int quality,
     74             int minLength, final int maxLength, boolean requirePasswordToDecrypt,
     75             boolean confirmCredentials) {
     76         Intent intent = new Intent().setClass(context, ChooseLockPassword.class);
     77         intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality);
     78         intent.putExtra(PASSWORD_MIN_KEY, minLength);
     79         intent.putExtra(PASSWORD_MAX_KEY, maxLength);
     80         intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials);
     81         intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePasswordToDecrypt);
     82         return intent;
     83     }
     84 
     85     public static Intent createIntent(Context context, int quality,
     86             int minLength, final int maxLength, boolean requirePasswordToDecrypt,
     87             boolean confirmCredentials, int userId) {
     88         Intent intent = createIntent(context, quality, minLength, maxLength,
     89                 requirePasswordToDecrypt, confirmCredentials);
     90         intent.putExtra(Intent.EXTRA_USER_ID, userId);
     91         return intent;
     92     }
     93 
     94     public static Intent createIntent(Context context, int quality,
     95             int minLength, final int maxLength, boolean requirePasswordToDecrypt, String password) {
     96         Intent intent = createIntent(context, quality, minLength, maxLength,
     97                 requirePasswordToDecrypt, false);
     98         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, password);
     99         return intent;
    100     }
    101 
    102     public static Intent createIntent(Context context, int quality, int minLength,
    103             int maxLength, boolean requirePasswordToDecrypt, String password, int userId) {
    104         Intent intent = createIntent(context, quality, minLength, maxLength,
    105                 requirePasswordToDecrypt, password);
    106         intent.putExtra(Intent.EXTRA_USER_ID, userId);
    107         return intent;
    108     }
    109 
    110     public static Intent createIntent(Context context, int quality,
    111             int minLength, final int maxLength, boolean requirePasswordToDecrypt, long challenge) {
    112         Intent intent = createIntent(context, quality, minLength, maxLength,
    113                 requirePasswordToDecrypt, false);
    114         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true);
    115         intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge);
    116         return intent;
    117     }
    118 
    119     public static Intent createIntent(Context context, int quality, int minLength,
    120             int maxLength, boolean requirePasswordToDecrypt, long challenge, int userId) {
    121         Intent intent = createIntent(context, quality, minLength, maxLength,
    122                 requirePasswordToDecrypt, challenge);
    123         intent.putExtra(Intent.EXTRA_USER_ID, userId);
    124         return intent;
    125     }
    126 
    127     @Override
    128     protected boolean isValidFragment(String fragmentName) {
    129         if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true;
    130         return false;
    131     }
    132 
    133     /* package */ Class<? extends Fragment> getFragmentClass() {
    134         return ChooseLockPasswordFragment.class;
    135     }
    136 
    137     @Override
    138     protected void onCreate(Bundle savedInstanceState) {
    139         // TODO: Fix on phones
    140         // Disable IME on our window since we provide our own keyboard
    141         //getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
    142                 //WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
    143         super.onCreate(savedInstanceState);
    144         CharSequence msg = getText(R.string.lockpassword_choose_your_password_header);
    145         setTitle(msg);
    146     }
    147 
    148     public static class ChooseLockPasswordFragment extends InstrumentedFragment
    149             implements OnClickListener, OnEditorActionListener,  TextWatcher,
    150             SaveAndFinishWorker.Listener {
    151         private static final String KEY_FIRST_PIN = "first_pin";
    152         private static final String KEY_UI_STAGE = "ui_stage";
    153         private static final String KEY_CURRENT_PASSWORD = "current_password";
    154         private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
    155 
    156         private String mCurrentPassword;
    157         private String mChosenPassword;
    158         private boolean mHasChallenge;
    159         private long mChallenge;
    160         private TextView mPasswordEntry;
    161         private TextViewInputDisabler mPasswordEntryInputDisabler;
    162         private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
    163         private int mPasswordMaxLength = 16;
    164         private int mPasswordMinLetters = 0;
    165         private int mPasswordMinUpperCase = 0;
    166         private int mPasswordMinLowerCase = 0;
    167         private int mPasswordMinSymbols = 0;
    168         private int mPasswordMinNumeric = 0;
    169         private int mPasswordMinNonLetter = 0;
    170         private LockPatternUtils mLockPatternUtils;
    171         private SaveAndFinishWorker mSaveAndFinishWorker;
    172         private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
    173         private ChooseLockSettingsHelper mChooseLockSettingsHelper;
    174         private Stage mUiStage = Stage.Introduction;
    175 
    176         private TextView mHeaderText;
    177         private String mFirstPin;
    178         private KeyboardView mKeyboardView;
    179         private PasswordEntryKeyboardHelper mKeyboardHelper;
    180         private boolean mIsAlphaMode;
    181         private Button mCancelButton;
    182         private Button mNextButton;
    183         private static final int CONFIRM_EXISTING_REQUEST = 58;
    184         static final int RESULT_FINISHED = RESULT_FIRST_USER;
    185         private static final long ERROR_MESSAGE_TIMEOUT = 3000;
    186         private static final int MSG_SHOW_ERROR = 1;
    187 
    188         private int mUserId;
    189         private boolean mHideDrawer = false;
    190 
    191         private Handler mHandler = new Handler() {
    192             @Override
    193             public void handleMessage(Message msg) {
    194                 if (msg.what == MSG_SHOW_ERROR) {
    195                     updateStage((Stage) msg.obj);
    196                 }
    197             }
    198         };
    199 
    200         /**
    201          * Keep track internally of where the user is in choosing a pattern.
    202          */
    203         protected enum Stage {
    204 
    205             Introduction(R.string.lockpassword_choose_your_password_header,
    206                     R.string.lockpassword_choose_your_pin_header,
    207                     R.string.lockpassword_continue_label),
    208 
    209             NeedToConfirm(R.string.lockpassword_confirm_your_password_header,
    210                     R.string.lockpassword_confirm_your_pin_header,
    211                     R.string.lockpassword_ok_label),
    212 
    213             ConfirmWrong(R.string.lockpassword_confirm_passwords_dont_match,
    214                     R.string.lockpassword_confirm_pins_dont_match,
    215                     R.string.lockpassword_continue_label);
    216 
    217             Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) {
    218                 this.alphaHint = hintInAlpha;
    219                 this.numericHint = hintInNumeric;
    220                 this.buttonText = nextButtonText;
    221             }
    222 
    223             public final int alphaHint;
    224             public final int numericHint;
    225             public final int buttonText;
    226         }
    227 
    228         // required constructor for fragments
    229         public ChooseLockPasswordFragment() {
    230 
    231         }
    232 
    233         @Override
    234         public void onCreate(Bundle savedInstanceState) {
    235             super.onCreate(savedInstanceState);
    236             mLockPatternUtils = new LockPatternUtils(getActivity());
    237             Intent intent = getActivity().getIntent();
    238             if (!(getActivity() instanceof ChooseLockPassword)) {
    239                 throw new SecurityException("Fragment contained in wrong activity");
    240             }
    241             // Only take this argument into account if it belongs to the current profile.
    242             mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
    243             mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
    244                     mRequestedQuality), mLockPatternUtils.getRequestedPasswordQuality(
    245                     mUserId));
    246             mPasswordMinLength = Math.max(Math.max(
    247                     LockPatternUtils.MIN_LOCK_PASSWORD_SIZE,
    248                     intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)),
    249                     mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId));
    250             mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength);
    251             mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY,
    252                     mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters(
    253                     mUserId));
    254             mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY,
    255                     mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase(
    256                     mUserId));
    257             mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY,
    258                     mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase(
    259                     mUserId));
    260             mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY,
    261                     mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric(
    262                     mUserId));
    263             mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY,
    264                     mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols(
    265                     mUserId));
    266             mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY,
    267                     mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter(
    268                     mUserId));
    269 
    270             mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
    271             mHideDrawer = getActivity().getIntent().getBooleanExtra(EXTRA_HIDE_DRAWER, false);
    272 
    273             if (intent.getBooleanExtra(
    274                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) {
    275                 SaveAndFinishWorker w = new SaveAndFinishWorker();
    276                 final boolean required = getActivity().getIntent().getBooleanExtra(
    277                         EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
    278                 String current = intent.getStringExtra(
    279                         ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
    280                 w.setBlocking(true);
    281                 w.setListener(this);
    282                 w.start(mChooseLockSettingsHelper.utils(), required,
    283                         false, 0, current, current, mRequestedQuality, mUserId);
    284             }
    285         }
    286 
    287         @Override
    288         public View onCreateView(LayoutInflater inflater, ViewGroup container,
    289                 Bundle savedInstanceState) {
    290             return inflater.inflate(R.layout.choose_lock_password, container, false);
    291         }
    292 
    293         @Override
    294         public void onViewCreated(View view, Bundle savedInstanceState) {
    295             super.onViewCreated(view, savedInstanceState);
    296 
    297             mCancelButton = (Button) view.findViewById(R.id.cancel_button);
    298             mCancelButton.setOnClickListener(this);
    299             mNextButton = (Button) view.findViewById(R.id.next_button);
    300             mNextButton.setOnClickListener(this);
    301 
    302             mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
    303                     || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality
    304                     || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality;
    305             mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard);
    306             mPasswordEntry = (TextView) view.findViewById(R.id.password_entry);
    307             mPasswordEntry.setOnEditorActionListener(this);
    308             mPasswordEntry.addTextChangedListener(this);
    309             mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
    310 
    311             final Activity activity = getActivity();
    312             mKeyboardHelper = new PasswordEntryKeyboardHelper(activity,
    313                     mKeyboardView, mPasswordEntry);
    314             mKeyboardHelper.setKeyboardMode(mIsAlphaMode ?
    315                     PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
    316                     : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
    317 
    318             mHeaderText = (TextView) view.findViewById(R.id.headerText);
    319             mKeyboardView.requestFocus();
    320 
    321             int currentType = mPasswordEntry.getInputType();
    322             mPasswordEntry.setInputType(mIsAlphaMode ? currentType
    323                     : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
    324 
    325             Intent intent = getActivity().getIntent();
    326             final boolean confirmCredentials = intent.getBooleanExtra(
    327                     ChooseLockGeneric.CONFIRM_CREDENTIALS, true);
    328             mCurrentPassword = intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
    329             mHasChallenge = intent.getBooleanExtra(
    330                     ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
    331             mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
    332             if (savedInstanceState == null) {
    333                 updateStage(Stage.Introduction);
    334                 if (confirmCredentials) {
    335                     mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST,
    336                             getString(R.string.unlock_set_unlock_launch_picker_title), true,
    337                             mUserId);
    338                 }
    339             } else {
    340                 // restore from previous state
    341                 mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN);
    342                 final String state = savedInstanceState.getString(KEY_UI_STAGE);
    343                 if (state != null) {
    344                     mUiStage = Stage.valueOf(state);
    345                     updateStage(mUiStage);
    346                 }
    347 
    348                 if (mCurrentPassword == null) {
    349                     mCurrentPassword = savedInstanceState.getString(KEY_CURRENT_PASSWORD);
    350                 }
    351 
    352                 // Re-attach to the exiting worker if there is one.
    353                 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
    354                         FRAGMENT_TAG_SAVE_AND_FINISH);
    355             }
    356             if (activity instanceof SettingsActivity) {
    357                 final SettingsActivity sa = (SettingsActivity) activity;
    358                 int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header
    359                         : R.string.lockpassword_choose_your_pin_header;
    360                 CharSequence title = getText(id);
    361                 sa.setTitle(title);
    362             }
    363         }
    364 
    365         @Override
    366         protected int getMetricsCategory() {
    367             return MetricsEvent.CHOOSE_LOCK_PASSWORD;
    368         }
    369 
    370         @Override
    371         public void onResume() {
    372             super.onResume();
    373             updateStage(mUiStage);
    374             if (mSaveAndFinishWorker != null) {
    375                 mSaveAndFinishWorker.setListener(this);
    376             } else {
    377                 mKeyboardView.requestFocus();
    378             }
    379         }
    380 
    381         @Override
    382         public void onPause() {
    383             mHandler.removeMessages(MSG_SHOW_ERROR);
    384             if (mSaveAndFinishWorker != null) {
    385                 mSaveAndFinishWorker.setListener(null);
    386             }
    387 
    388             super.onPause();
    389         }
    390 
    391         @Override
    392         public void onSaveInstanceState(Bundle outState) {
    393             super.onSaveInstanceState(outState);
    394             outState.putString(KEY_UI_STAGE, mUiStage.name());
    395             outState.putString(KEY_FIRST_PIN, mFirstPin);
    396             outState.putString(KEY_CURRENT_PASSWORD, mCurrentPassword);
    397         }
    398 
    399         @Override
    400         public void onActivityResult(int requestCode, int resultCode,
    401                 Intent data) {
    402             super.onActivityResult(requestCode, resultCode, data);
    403             switch (requestCode) {
    404                 case CONFIRM_EXISTING_REQUEST:
    405                     if (resultCode != Activity.RESULT_OK) {
    406                         getActivity().setResult(RESULT_FINISHED);
    407                         getActivity().finish();
    408                     } else {
    409                         mCurrentPassword = data.getStringExtra(
    410                                 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
    411                     }
    412                     break;
    413             }
    414         }
    415 
    416         protected Intent getRedactionInterstitialIntent(Context context) {
    417             return RedactionInterstitial.createStartIntent(context, mUserId);
    418         }
    419 
    420         protected void updateStage(Stage stage) {
    421             final Stage previousStage = mUiStage;
    422             mUiStage = stage;
    423             updateUi();
    424 
    425             // If the stage changed, announce the header for accessibility. This
    426             // is a no-op when accessibility is disabled.
    427             if (previousStage != stage) {
    428                 mHeaderText.announceForAccessibility(mHeaderText.getText());
    429             }
    430         }
    431 
    432         /**
    433          * Validates PIN and returns a message to display if PIN fails test.
    434          * @param password the raw password the user typed in
    435          * @return error message to show to user or null if password is OK
    436          */
    437         private String validatePassword(String password) {
    438             if (password.length() < mPasswordMinLength) {
    439                 return getString(mIsAlphaMode ?
    440                         R.string.lockpassword_password_too_short
    441                         : R.string.lockpassword_pin_too_short, mPasswordMinLength);
    442             }
    443             if (password.length() > mPasswordMaxLength) {
    444                 return getString(mIsAlphaMode ?
    445                         R.string.lockpassword_password_too_long
    446                         : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1);
    447             }
    448             int letters = 0;
    449             int numbers = 0;
    450             int lowercase = 0;
    451             int symbols = 0;
    452             int uppercase = 0;
    453             int nonletter = 0;
    454             for (int i = 0; i < password.length(); i++) {
    455                 char c = password.charAt(i);
    456                 // allow non control Latin-1 characters only
    457                 if (c < 32 || c > 127) {
    458                     return getString(R.string.lockpassword_illegal_character);
    459                 }
    460                 if (c >= '0' && c <= '9') {
    461                     numbers++;
    462                     nonletter++;
    463                 } else if (c >= 'A' && c <= 'Z') {
    464                     letters++;
    465                     uppercase++;
    466                 } else if (c >= 'a' && c <= 'z') {
    467                     letters++;
    468                     lowercase++;
    469                 } else {
    470                     symbols++;
    471                     nonletter++;
    472                 }
    473             }
    474             if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC == mRequestedQuality
    475                     || DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality) {
    476                 if (letters > 0 || symbols > 0) {
    477                     // This shouldn't be possible unless user finds some way to bring up
    478                     // soft keyboard
    479                     return getString(R.string.lockpassword_pin_contains_non_digits);
    480                 }
    481                 // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
    482                 final int sequence = LockPatternUtils.maxLengthSequence(password);
    483                 if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality
    484                         && sequence > LockPatternUtils.MAX_ALLOWED_SEQUENCE) {
    485                     return getString(R.string.lockpassword_pin_no_sequential_digits);
    486                 }
    487             } else if (DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality) {
    488                 if (letters < mPasswordMinLetters) {
    489                     return String.format(getResources().getQuantityString(
    490                             R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters),
    491                             mPasswordMinLetters);
    492                 } else if (numbers < mPasswordMinNumeric) {
    493                     return String.format(getResources().getQuantityString(
    494                             R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric),
    495                             mPasswordMinNumeric);
    496                 } else if (lowercase < mPasswordMinLowerCase) {
    497                     return String.format(getResources().getQuantityString(
    498                             R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase),
    499                             mPasswordMinLowerCase);
    500                 } else if (uppercase < mPasswordMinUpperCase) {
    501                     return String.format(getResources().getQuantityString(
    502                             R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase),
    503                             mPasswordMinUpperCase);
    504                 } else if (symbols < mPasswordMinSymbols) {
    505                     return String.format(getResources().getQuantityString(
    506                             R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols),
    507                             mPasswordMinSymbols);
    508                 } else if (nonletter < mPasswordMinNonLetter) {
    509                     return String.format(getResources().getQuantityString(
    510                             R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter),
    511                             mPasswordMinNonLetter);
    512                 }
    513             } else {
    514                 final boolean alphabetic = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
    515                         == mRequestedQuality;
    516                 final boolean alphanumeric = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
    517                         == mRequestedQuality;
    518                 if ((alphabetic || alphanumeric) && letters == 0) {
    519                     return getString(R.string.lockpassword_password_requires_alpha);
    520                 }
    521                 if (alphanumeric && numbers == 0) {
    522                     return getString(R.string.lockpassword_password_requires_digit);
    523                 }
    524             }
    525             if(mLockPatternUtils.checkPasswordHistory(password, mUserId)) {
    526                 return getString(mIsAlphaMode ? R.string.lockpassword_password_recently_used
    527                         : R.string.lockpassword_pin_recently_used);
    528             }
    529 
    530             return null;
    531         }
    532 
    533         public void handleNext() {
    534             if (mSaveAndFinishWorker != null) return;
    535             mChosenPassword = mPasswordEntry.getText().toString();
    536             if (TextUtils.isEmpty(mChosenPassword)) {
    537                 return;
    538             }
    539             String errorMsg = null;
    540             if (mUiStage == Stage.Introduction) {
    541                 errorMsg = validatePassword(mChosenPassword);
    542                 if (errorMsg == null) {
    543                     mFirstPin = mChosenPassword;
    544                     mPasswordEntry.setText("");
    545                     updateStage(Stage.NeedToConfirm);
    546                 }
    547             } else if (mUiStage == Stage.NeedToConfirm) {
    548                 if (mFirstPin.equals(mChosenPassword)) {
    549                     startSaveAndFinish();
    550                 } else {
    551                     CharSequence tmp = mPasswordEntry.getText();
    552                     if (tmp != null) {
    553                         Selection.setSelection((Spannable) tmp, 0, tmp.length());
    554                     }
    555                     updateStage(Stage.ConfirmWrong);
    556                 }
    557             }
    558             if (errorMsg != null) {
    559                 showError(errorMsg, mUiStage);
    560             }
    561         }
    562 
    563         protected void setNextEnabled(boolean enabled) {
    564             mNextButton.setEnabled(enabled);
    565         }
    566 
    567         protected void setNextText(int text) {
    568             mNextButton.setText(text);
    569         }
    570 
    571         public void onClick(View v) {
    572             switch (v.getId()) {
    573                 case R.id.next_button:
    574                     handleNext();
    575                     break;
    576 
    577                 case R.id.cancel_button:
    578                     getActivity().finish();
    579                     break;
    580             }
    581         }
    582 
    583         private void showError(String msg, final Stage next) {
    584             mHeaderText.setText(msg);
    585             mHeaderText.announceForAccessibility(mHeaderText.getText());
    586             Message mesg = mHandler.obtainMessage(MSG_SHOW_ERROR, next);
    587             mHandler.removeMessages(MSG_SHOW_ERROR);
    588             mHandler.sendMessageDelayed(mesg, ERROR_MESSAGE_TIMEOUT);
    589         }
    590 
    591         public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    592             // Check if this was the result of hitting the enter or "done" key
    593             if (actionId == EditorInfo.IME_NULL
    594                     || actionId == EditorInfo.IME_ACTION_DONE
    595                     || actionId == EditorInfo.IME_ACTION_NEXT) {
    596                 handleNext();
    597                 return true;
    598             }
    599             return false;
    600         }
    601 
    602         /**
    603          * Update the hint based on current Stage and length of password entry
    604          */
    605         private void updateUi() {
    606             final boolean canInput = mSaveAndFinishWorker == null;
    607             String password = mPasswordEntry.getText().toString();
    608             final int length = password.length();
    609             if (mUiStage == Stage.Introduction) {
    610                 if (length < mPasswordMinLength) {
    611                     String msg = getString(mIsAlphaMode ? R.string.lockpassword_password_too_short
    612                             : R.string.lockpassword_pin_too_short, mPasswordMinLength);
    613                     mHeaderText.setText(msg);
    614                     setNextEnabled(false);
    615                 } else {
    616                     String error = validatePassword(password);
    617                     if (error != null) {
    618                         mHeaderText.setText(error);
    619                         setNextEnabled(false);
    620                     } else {
    621                         mHeaderText.setText(null);
    622                         setNextEnabled(true);
    623                     }
    624                 }
    625             } else {
    626                 mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint);
    627                 setNextEnabled(canInput && length > 0);
    628             }
    629             setNextText(mUiStage.buttonText);
    630             mPasswordEntryInputDisabler.setInputEnabled(canInput);
    631         }
    632 
    633         public void afterTextChanged(Editable s) {
    634             // Changing the text while error displayed resets to NeedToConfirm state
    635             if (mUiStage == Stage.ConfirmWrong) {
    636                 mUiStage = Stage.NeedToConfirm;
    637             }
    638             updateUi();
    639         }
    640 
    641         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    642 
    643         }
    644 
    645         public void onTextChanged(CharSequence s, int start, int before, int count) {
    646 
    647         }
    648 
    649         private void startSaveAndFinish() {
    650             if (mSaveAndFinishWorker != null) {
    651                 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
    652                 return;
    653             }
    654 
    655             mPasswordEntryInputDisabler.setInputEnabled(false);
    656             setNextEnabled(false);
    657 
    658             mSaveAndFinishWorker = new SaveAndFinishWorker();
    659             mSaveAndFinishWorker.setListener(this);
    660 
    661             getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
    662                     FRAGMENT_TAG_SAVE_AND_FINISH).commit();
    663             getFragmentManager().executePendingTransactions();
    664 
    665             final boolean required = getActivity().getIntent().getBooleanExtra(
    666                     EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
    667             mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge,
    668                     mChosenPassword, mCurrentPassword, mRequestedQuality, mUserId);
    669         }
    670 
    671         @Override
    672         public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
    673             getActivity().setResult(RESULT_FINISHED, resultData);
    674 
    675             if (!wasSecureBefore) {
    676                 Intent intent = getRedactionInterstitialIntent(getActivity());
    677                 if (intent != null) {
    678                     intent.putExtra(EXTRA_HIDE_DRAWER, mHideDrawer);
    679                     startActivity(intent);
    680                 }
    681             }
    682             getActivity().finish();
    683         }
    684     }
    685 
    686     private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
    687 
    688         private String mChosenPassword;
    689         private String mCurrentPassword;
    690         private int mRequestedQuality;
    691 
    692         public void start(LockPatternUtils utils, boolean required,
    693                 boolean hasChallenge, long challenge,
    694                 String chosenPassword, String currentPassword, int requestedQuality, int userId) {
    695             prepare(utils, required, hasChallenge, challenge, userId);
    696 
    697             mChosenPassword = chosenPassword;
    698             mCurrentPassword = currentPassword;
    699             mRequestedQuality = requestedQuality;
    700             mUserId = userId;
    701 
    702             start();
    703         }
    704 
    705         @Override
    706         protected Intent saveAndVerifyInBackground() {
    707             Intent result = null;
    708             mUtils.saveLockPassword(mChosenPassword, mCurrentPassword, mRequestedQuality,
    709                     mUserId);
    710 
    711             if (mHasChallenge) {
    712                 byte[] token;
    713                 try {
    714                     token = mUtils.verifyPassword(mChosenPassword, mChallenge, mUserId);
    715                 } catch (RequestThrottledException e) {
    716                     token = null;
    717                 }
    718 
    719                 if (token == null) {
    720                     Log.e(TAG, "critical: no token returned for known good password.");
    721                 }
    722 
    723                 result = new Intent();
    724                 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
    725             }
    726 
    727             return result;
    728         }
    729     }
    730 }
    731