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