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