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