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 com.android.internal.widget.LockPatternUtils;
     20 import com.android.internal.widget.PasswordEntryKeyboardHelper;
     21 import com.android.internal.widget.PasswordEntryKeyboardView;
     22 import com.android.settings.notification.RedactionInterstitial;
     23 
     24 import android.app.Activity;
     25 import android.app.Fragment;
     26 import android.app.admin.DevicePolicyManager;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.inputmethodservice.KeyboardView;
     30 import android.os.Bundle;
     31 import android.os.Handler;
     32 import android.os.Message;
     33 import android.text.Editable;
     34 import android.text.InputType;
     35 import android.text.Selection;
     36 import android.text.Spannable;
     37 import android.text.TextUtils;
     38 import android.text.TextWatcher;
     39 import android.view.KeyEvent;
     40 import android.view.LayoutInflater;
     41 import android.view.View;
     42 import android.view.ViewGroup;
     43 import android.view.View.OnClickListener;
     44 import android.view.inputmethod.EditorInfo;
     45 import android.widget.Button;
     46 import android.widget.TextView;
     47 import android.widget.TextView.OnEditorActionListener;
     48 
     49 public class ChooseLockPassword extends SettingsActivity {
     50     public static final String PASSWORD_MIN_KEY = "lockscreen.password_min";
     51     public static final String PASSWORD_MAX_KEY = "lockscreen.password_max";
     52     public static final String PASSWORD_MIN_LETTERS_KEY = "lockscreen.password_min_letters";
     53     public static final String PASSWORD_MIN_LOWERCASE_KEY = "lockscreen.password_min_lowercase";
     54     public static final String PASSWORD_MIN_UPPERCASE_KEY = "lockscreen.password_min_uppercase";
     55     public static final String PASSWORD_MIN_NUMERIC_KEY = "lockscreen.password_min_numeric";
     56     public static final String PASSWORD_MIN_SYMBOLS_KEY = "lockscreen.password_min_symbols";
     57     public static final String PASSWORD_MIN_NONLETTER_KEY = "lockscreen.password_min_nonletter";
     58 
     59     @Override
     60     public Intent getIntent() {
     61         Intent modIntent = new Intent(super.getIntent());
     62         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
     63         return modIntent;
     64     }
     65 
     66     public static Intent createIntent(Context context, int quality, final boolean isFallback,
     67             int minLength, final int maxLength, boolean requirePasswordToDecrypt,
     68             boolean confirmCredentials) {
     69         Intent intent = new Intent().setClass(context, ChooseLockPassword.class);
     70         intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality);
     71         intent.putExtra(PASSWORD_MIN_KEY, minLength);
     72         intent.putExtra(PASSWORD_MAX_KEY, maxLength);
     73         intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials);
     74         intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, isFallback);
     75         intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePasswordToDecrypt);
     76         return intent;
     77     }
     78 
     79     @Override
     80     protected boolean isValidFragment(String fragmentName) {
     81         if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true;
     82         return false;
     83     }
     84 
     85     /* package */ Class<? extends Fragment> getFragmentClass() {
     86         return ChooseLockPasswordFragment.class;
     87     }
     88 
     89     @Override
     90     public void onCreate(Bundle savedInstanceState) {
     91         // TODO: Fix on phones
     92         // Disable IME on our window since we provide our own keyboard
     93         //getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
     94                 //WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
     95         super.onCreate(savedInstanceState);
     96         CharSequence msg = getText(R.string.lockpassword_choose_your_password_header);
     97         setTitle(msg);
     98     }
     99 
    100     public static class ChooseLockPasswordFragment extends Fragment
    101             implements OnClickListener, OnEditorActionListener,  TextWatcher {
    102         private static final String KEY_FIRST_PIN = "first_pin";
    103         private static final String KEY_UI_STAGE = "ui_stage";
    104         private TextView mPasswordEntry;
    105         private int mPasswordMinLength = 4;
    106         private int mPasswordMaxLength = 16;
    107         private int mPasswordMinLetters = 0;
    108         private int mPasswordMinUpperCase = 0;
    109         private int mPasswordMinLowerCase = 0;
    110         private int mPasswordMinSymbols = 0;
    111         private int mPasswordMinNumeric = 0;
    112         private int mPasswordMinNonLetter = 0;
    113         private LockPatternUtils mLockPatternUtils;
    114         private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
    115         private ChooseLockSettingsHelper mChooseLockSettingsHelper;
    116         private Stage mUiStage = Stage.Introduction;
    117         private boolean mDone = false;
    118         private TextView mHeaderText;
    119         private String mFirstPin;
    120         private KeyboardView mKeyboardView;
    121         private PasswordEntryKeyboardHelper mKeyboardHelper;
    122         private boolean mIsAlphaMode;
    123         private Button mCancelButton;
    124         private Button mNextButton;
    125         private static final int CONFIRM_EXISTING_REQUEST = 58;
    126         static final int RESULT_FINISHED = RESULT_FIRST_USER;
    127         private static final long ERROR_MESSAGE_TIMEOUT = 3000;
    128         private static final int MSG_SHOW_ERROR = 1;
    129 
    130         private Handler mHandler = new Handler() {
    131             @Override
    132             public void handleMessage(Message msg) {
    133                 if (msg.what == MSG_SHOW_ERROR) {
    134                     updateStage((Stage) msg.obj);
    135                 }
    136             }
    137         };
    138 
    139         /**
    140          * Keep track internally of where the user is in choosing a pattern.
    141          */
    142         protected enum Stage {
    143 
    144             Introduction(R.string.lockpassword_choose_your_password_header,
    145                     R.string.lockpassword_choose_your_pin_header,
    146                     R.string.lockpassword_continue_label),
    147 
    148             NeedToConfirm(R.string.lockpassword_confirm_your_password_header,
    149                     R.string.lockpassword_confirm_your_pin_header,
    150                     R.string.lockpassword_ok_label),
    151 
    152             ConfirmWrong(R.string.lockpassword_confirm_passwords_dont_match,
    153                     R.string.lockpassword_confirm_pins_dont_match,
    154                     R.string.lockpassword_continue_label);
    155 
    156             Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) {
    157                 this.alphaHint = hintInAlpha;
    158                 this.numericHint = hintInNumeric;
    159                 this.buttonText = nextButtonText;
    160             }
    161 
    162             public final int alphaHint;
    163             public final int numericHint;
    164             public final int buttonText;
    165         }
    166 
    167         // required constructor for fragments
    168         public ChooseLockPasswordFragment() {
    169 
    170         }
    171 
    172         @Override
    173         public void onCreate(Bundle savedInstanceState) {
    174             super.onCreate(savedInstanceState);
    175             mLockPatternUtils = new LockPatternUtils(getActivity());
    176             Intent intent = getActivity().getIntent();
    177             if (!(getActivity() instanceof ChooseLockPassword)) {
    178                 throw new SecurityException("Fragment contained in wrong activity");
    179             }
    180             mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY,
    181                     mRequestedQuality), mLockPatternUtils.getRequestedPasswordQuality());
    182             mPasswordMinLength = Math.max(
    183                     intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength), mLockPatternUtils
    184                             .getRequestedMinimumPasswordLength());
    185             mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength);
    186             mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY,
    187                     mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters());
    188             mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY,
    189                     mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase());
    190             mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY,
    191                     mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase());
    192             mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY,
    193                     mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric());
    194             mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY,
    195                     mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols());
    196             mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY,
    197                     mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter());
    198 
    199             mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
    200         }
    201 
    202         @Override
    203         public View onCreateView(LayoutInflater inflater, ViewGroup container,
    204                 Bundle savedInstanceState) {
    205             return inflater.inflate(R.layout.choose_lock_password, container, false);
    206         }
    207 
    208         @Override
    209         public void onViewCreated(View view, Bundle savedInstanceState) {
    210             super.onViewCreated(view, savedInstanceState);
    211 
    212             mCancelButton = (Button) view.findViewById(R.id.cancel_button);
    213             mCancelButton.setOnClickListener(this);
    214             mNextButton = (Button) view.findViewById(R.id.next_button);
    215             mNextButton.setOnClickListener(this);
    216 
    217             mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality
    218                     || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality
    219                     || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality;
    220             mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard);
    221             mPasswordEntry = (TextView) view.findViewById(R.id.password_entry);
    222             mPasswordEntry.setOnEditorActionListener(this);
    223             mPasswordEntry.addTextChangedListener(this);
    224 
    225             final Activity activity = getActivity();
    226             mKeyboardHelper = new PasswordEntryKeyboardHelper(activity,
    227                     mKeyboardView, mPasswordEntry);
    228             mKeyboardHelper.setKeyboardMode(mIsAlphaMode ?
    229                     PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA
    230                     : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC);
    231 
    232             mHeaderText = (TextView) view.findViewById(R.id.headerText);
    233             mKeyboardView.requestFocus();
    234 
    235             int currentType = mPasswordEntry.getInputType();
    236             mPasswordEntry.setInputType(mIsAlphaMode ? currentType
    237                     : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
    238 
    239             Intent intent = getActivity().getIntent();
    240             final boolean confirmCredentials = intent.getBooleanExtra("confirm_credentials", true);
    241             if (savedInstanceState == null) {
    242                 updateStage(Stage.Introduction);
    243                 if (confirmCredentials) {
    244                     mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST,
    245                             null, null);
    246                 }
    247             } else {
    248                 mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN);
    249                 final String state = savedInstanceState.getString(KEY_UI_STAGE);
    250                 if (state != null) {
    251                     mUiStage = Stage.valueOf(state);
    252                     updateStage(mUiStage);
    253                 }
    254             }
    255             mDone = false;
    256             if (activity instanceof SettingsActivity) {
    257                 final SettingsActivity sa = (SettingsActivity) activity;
    258                 int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header
    259                         : R.string.lockpassword_choose_your_pin_header;
    260                 CharSequence title = getText(id);
    261                 sa.setTitle(title);
    262             }
    263         }
    264 
    265         @Override
    266         public void onResume() {
    267             super.onResume();
    268             updateStage(mUiStage);
    269             mKeyboardView.requestFocus();
    270         }
    271 
    272         @Override
    273         public void onPause() {
    274             mHandler.removeMessages(MSG_SHOW_ERROR);
    275 
    276             super.onPause();
    277         }
    278 
    279         @Override
    280         public void onSaveInstanceState(Bundle outState) {
    281             super.onSaveInstanceState(outState);
    282             outState.putString(KEY_UI_STAGE, mUiStage.name());
    283             outState.putString(KEY_FIRST_PIN, mFirstPin);
    284         }
    285 
    286         @Override
    287         public void onActivityResult(int requestCode, int resultCode,
    288                 Intent data) {
    289             super.onActivityResult(requestCode, resultCode, data);
    290             switch (requestCode) {
    291                 case CONFIRM_EXISTING_REQUEST:
    292                     if (resultCode != Activity.RESULT_OK) {
    293                         getActivity().setResult(RESULT_FINISHED);
    294                         getActivity().finish();
    295                     }
    296                     break;
    297             }
    298         }
    299 
    300         protected Intent getRedactionInterstitialIntent(Context context) {
    301             return RedactionInterstitial.createStartIntent(context);
    302         }
    303 
    304         protected void updateStage(Stage stage) {
    305             final Stage previousStage = mUiStage;
    306             mUiStage = stage;
    307             updateUi();
    308 
    309             // If the stage changed, announce the header for accessibility. This
    310             // is a no-op when accessibility is disabled.
    311             if (previousStage != stage) {
    312                 mHeaderText.announceForAccessibility(mHeaderText.getText());
    313             }
    314         }
    315 
    316         /**
    317          * Validates PIN and returns a message to display if PIN fails test.
    318          * @param password the raw password the user typed in
    319          * @return error message to show to user or null if password is OK
    320          */
    321         private String validatePassword(String password) {
    322             if (password.length() < mPasswordMinLength) {
    323                 return getString(mIsAlphaMode ?
    324                         R.string.lockpassword_password_too_short
    325                         : R.string.lockpassword_pin_too_short, mPasswordMinLength);
    326             }
    327             if (password.length() > mPasswordMaxLength) {
    328                 return getString(mIsAlphaMode ?
    329                         R.string.lockpassword_password_too_long
    330                         : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1);
    331             }
    332             int letters = 0;
    333             int numbers = 0;
    334             int lowercase = 0;
    335             int symbols = 0;
    336             int uppercase = 0;
    337             int nonletter = 0;
    338             for (int i = 0; i < password.length(); i++) {
    339                 char c = password.charAt(i);
    340                 // allow non control Latin-1 characters only
    341                 if (c < 32 || c > 127) {
    342                     return getString(R.string.lockpassword_illegal_character);
    343                 }
    344                 if (c >= '0' && c <= '9') {
    345                     numbers++;
    346                     nonletter++;
    347                 } else if (c >= 'A' && c <= 'Z') {
    348                     letters++;
    349                     uppercase++;
    350                 } else if (c >= 'a' && c <= 'z') {
    351                     letters++;
    352                     lowercase++;
    353                 } else {
    354                     symbols++;
    355                     nonletter++;
    356                 }
    357             }
    358             if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC == mRequestedQuality
    359                     || DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality) {
    360                 if (letters > 0 || symbols > 0) {
    361                     // This shouldn't be possible unless user finds some way to bring up
    362                     // soft keyboard
    363                     return getString(R.string.lockpassword_pin_contains_non_digits);
    364                 }
    365                 // Check for repeated characters or sequences (e.g. '1234', '0000', '2468')
    366                 final int sequence = LockPatternUtils.maxLengthSequence(password);
    367                 if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality
    368                         && sequence > LockPatternUtils.MAX_ALLOWED_SEQUENCE) {
    369                     return getString(R.string.lockpassword_pin_no_sequential_digits);
    370                 }
    371             } else if (DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality) {
    372                 if (letters < mPasswordMinLetters) {
    373                     return String.format(getResources().getQuantityString(
    374                             R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters),
    375                             mPasswordMinLetters);
    376                 } else if (numbers < mPasswordMinNumeric) {
    377                     return String.format(getResources().getQuantityString(
    378                             R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric),
    379                             mPasswordMinNumeric);
    380                 } else if (lowercase < mPasswordMinLowerCase) {
    381                     return String.format(getResources().getQuantityString(
    382                             R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase),
    383                             mPasswordMinLowerCase);
    384                 } else if (uppercase < mPasswordMinUpperCase) {
    385                     return String.format(getResources().getQuantityString(
    386                             R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase),
    387                             mPasswordMinUpperCase);
    388                 } else if (symbols < mPasswordMinSymbols) {
    389                     return String.format(getResources().getQuantityString(
    390                             R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols),
    391                             mPasswordMinSymbols);
    392                 } else if (nonletter < mPasswordMinNonLetter) {
    393                     return String.format(getResources().getQuantityString(
    394                             R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter),
    395                             mPasswordMinNonLetter);
    396                 }
    397             } else {
    398                 final boolean alphabetic = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
    399                         == mRequestedQuality;
    400                 final boolean alphanumeric = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
    401                         == mRequestedQuality;
    402                 if ((alphabetic || alphanumeric) && letters == 0) {
    403                     return getString(R.string.lockpassword_password_requires_alpha);
    404                 }
    405                 if (alphanumeric && numbers == 0) {
    406                     return getString(R.string.lockpassword_password_requires_digit);
    407                 }
    408             }
    409             if(mLockPatternUtils.checkPasswordHistory(password)) {
    410                 return getString(mIsAlphaMode ? R.string.lockpassword_password_recently_used
    411                         : R.string.lockpassword_pin_recently_used);
    412             }
    413 
    414             return null;
    415         }
    416 
    417         public void handleNext() {
    418             if (mDone) return;
    419 
    420             final String pin = mPasswordEntry.getText().toString();
    421             if (TextUtils.isEmpty(pin)) {
    422                 return;
    423             }
    424             String errorMsg = null;
    425             if (mUiStage == Stage.Introduction) {
    426                 errorMsg = validatePassword(pin);
    427                 if (errorMsg == null) {
    428                     mFirstPin = pin;
    429                     mPasswordEntry.setText("");
    430                     updateStage(Stage.NeedToConfirm);
    431                 }
    432             } else if (mUiStage == Stage.NeedToConfirm) {
    433                 if (mFirstPin.equals(pin)) {
    434                     final boolean isFallback = getActivity().getIntent().getBooleanExtra(
    435                             LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
    436                     boolean wasSecureBefore = mLockPatternUtils.isSecure();
    437                     mLockPatternUtils.clearLock(isFallback);
    438                     final boolean required = getActivity().getIntent().getBooleanExtra(
    439                             EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
    440                     mLockPatternUtils.setCredentialRequiredToDecrypt(required);
    441                     mLockPatternUtils.saveLockPassword(pin, mRequestedQuality, isFallback);
    442                     getActivity().setResult(RESULT_FINISHED);
    443                     getActivity().finish();
    444                     mDone = true;
    445                     if (!wasSecureBefore) {
    446                         startActivity(getRedactionInterstitialIntent(getActivity()));
    447                     }
    448                 } else {
    449                     CharSequence tmp = mPasswordEntry.getText();
    450                     if (tmp != null) {
    451                         Selection.setSelection((Spannable) tmp, 0, tmp.length());
    452                     }
    453                     updateStage(Stage.ConfirmWrong);
    454                 }
    455             }
    456             if (errorMsg != null) {
    457                 showError(errorMsg, mUiStage);
    458             }
    459         }
    460 
    461         protected void setNextEnabled(boolean enabled) {
    462             mNextButton.setEnabled(enabled);
    463         }
    464 
    465         protected void setNextText(int text) {
    466             mNextButton.setText(text);
    467         }
    468 
    469         public void onClick(View v) {
    470             switch (v.getId()) {
    471                 case R.id.next_button:
    472                     handleNext();
    473                     break;
    474 
    475                 case R.id.cancel_button:
    476                     getActivity().finish();
    477                     break;
    478             }
    479         }
    480 
    481         private void showError(String msg, final Stage next) {
    482             mHeaderText.setText(msg);
    483             mHeaderText.announceForAccessibility(mHeaderText.getText());
    484             Message mesg = mHandler.obtainMessage(MSG_SHOW_ERROR, next);
    485             mHandler.removeMessages(MSG_SHOW_ERROR);
    486             mHandler.sendMessageDelayed(mesg, ERROR_MESSAGE_TIMEOUT);
    487         }
    488 
    489         public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
    490             // Check if this was the result of hitting the enter or "done" key
    491             if (actionId == EditorInfo.IME_NULL
    492                     || actionId == EditorInfo.IME_ACTION_DONE
    493                     || actionId == EditorInfo.IME_ACTION_NEXT) {
    494                 handleNext();
    495                 return true;
    496             }
    497             return false;
    498         }
    499 
    500         /**
    501          * Update the hint based on current Stage and length of password entry
    502          */
    503         private void updateUi() {
    504             String password = mPasswordEntry.getText().toString();
    505             final int length = password.length();
    506             if (mUiStage == Stage.Introduction && length > 0) {
    507                 if (length < mPasswordMinLength) {
    508                     String msg = getString(mIsAlphaMode ? R.string.lockpassword_password_too_short
    509                             : R.string.lockpassword_pin_too_short, mPasswordMinLength);
    510                     mHeaderText.setText(msg);
    511                     setNextEnabled(false);
    512                 } else {
    513                     String error = validatePassword(password);
    514                     if (error != null) {
    515                         mHeaderText.setText(error);
    516                         setNextEnabled(false);
    517                     } else {
    518                         mHeaderText.setText(R.string.lockpassword_press_continue);
    519                         setNextEnabled(true);
    520                     }
    521                 }
    522             } else {
    523                 mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint);
    524                 setNextEnabled(length > 0);
    525             }
    526             setNextText(mUiStage.buttonText);
    527         }
    528 
    529         public void afterTextChanged(Editable s) {
    530             // Changing the text while error displayed resets to NeedToConfirm state
    531             if (mUiStage == Stage.ConfirmWrong) {
    532                 mUiStage = Stage.NeedToConfirm;
    533             }
    534             updateUi();
    535         }
    536 
    537         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    538 
    539         }
    540 
    541         public void onTextChanged(CharSequence s, int start, int before, int count) {
    542 
    543         }
    544     }
    545 }
    546