ChooseLockPassword.java revision 4c00a611835e2257502ddcdd26b4e827c5b64f80
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; 22import com.android.settings.notification.RedactionInterstitial; 23 24import android.app.Activity; 25import android.app.Fragment; 26import android.app.admin.DevicePolicyManager; 27import android.content.Context; 28import android.content.Intent; 29import android.inputmethodservice.KeyboardView; 30import android.os.Bundle; 31import android.os.Handler; 32import android.os.Message; 33import android.text.Editable; 34import android.text.InputType; 35import android.text.Selection; 36import android.text.Spannable; 37import android.text.TextUtils; 38import android.text.TextWatcher; 39import android.view.KeyEvent; 40import android.view.LayoutInflater; 41import android.view.View; 42import android.view.ViewGroup; 43import android.view.View.OnClickListener; 44import android.view.inputmethod.EditorInfo; 45import android.widget.Button; 46import android.widget.TextView; 47import android.widget.TextView.OnEditorActionListener; 48 49public 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, 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(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePasswordToDecrypt); 75 return intent; 76 } 77 78 @Override 79 protected boolean isValidFragment(String fragmentName) { 80 if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true; 81 return false; 82 } 83 84 /* package */ Class<? extends Fragment> getFragmentClass() { 85 return ChooseLockPasswordFragment.class; 86 } 87 88 @Override 89 public void onCreate(Bundle savedInstanceState) { 90 // TODO: Fix on phones 91 // Disable IME on our window since we provide our own keyboard 92 //getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 93 //WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 94 super.onCreate(savedInstanceState); 95 CharSequence msg = getText(R.string.lockpassword_choose_your_password_header); 96 setTitle(msg); 97 } 98 99 public static class ChooseLockPasswordFragment extends Fragment 100 implements OnClickListener, OnEditorActionListener, TextWatcher { 101 private static final String KEY_FIRST_PIN = "first_pin"; 102 private static final String KEY_UI_STAGE = "ui_stage"; 103 private TextView mPasswordEntry; 104 private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE; 105 private int mPasswordMaxLength = 16; 106 private int mPasswordMinLetters = 0; 107 private int mPasswordMinUpperCase = 0; 108 private int mPasswordMinLowerCase = 0; 109 private int mPasswordMinSymbols = 0; 110 private int mPasswordMinNumeric = 0; 111 private int mPasswordMinNonLetter = 0; 112 private LockPatternUtils mLockPatternUtils; 113 private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 114 private ChooseLockSettingsHelper mChooseLockSettingsHelper; 115 private Stage mUiStage = Stage.Introduction; 116 private boolean mDone = false; 117 private TextView mHeaderText; 118 private String mFirstPin; 119 private KeyboardView mKeyboardView; 120 private PasswordEntryKeyboardHelper mKeyboardHelper; 121 private boolean mIsAlphaMode; 122 private Button mCancelButton; 123 private Button mNextButton; 124 private static final int CONFIRM_EXISTING_REQUEST = 58; 125 static final int RESULT_FINISHED = RESULT_FIRST_USER; 126 private static final long ERROR_MESSAGE_TIMEOUT = 3000; 127 private static final int MSG_SHOW_ERROR = 1; 128 129 private Handler mHandler = new Handler() { 130 @Override 131 public void handleMessage(Message msg) { 132 if (msg.what == MSG_SHOW_ERROR) { 133 updateStage((Stage) msg.obj); 134 } 135 } 136 }; 137 138 /** 139 * Keep track internally of where the user is in choosing a pattern. 140 */ 141 protected enum Stage { 142 143 Introduction(R.string.lockpassword_choose_your_password_header, 144 R.string.lockpassword_choose_your_pin_header, 145 R.string.lockpassword_continue_label), 146 147 NeedToConfirm(R.string.lockpassword_confirm_your_password_header, 148 R.string.lockpassword_confirm_your_pin_header, 149 R.string.lockpassword_ok_label), 150 151 ConfirmWrong(R.string.lockpassword_confirm_passwords_dont_match, 152 R.string.lockpassword_confirm_pins_dont_match, 153 R.string.lockpassword_continue_label); 154 155 Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) { 156 this.alphaHint = hintInAlpha; 157 this.numericHint = hintInNumeric; 158 this.buttonText = nextButtonText; 159 } 160 161 public final int alphaHint; 162 public final int numericHint; 163 public final int buttonText; 164 } 165 166 // required constructor for fragments 167 public ChooseLockPasswordFragment() { 168 169 } 170 171 @Override 172 public void onCreate(Bundle savedInstanceState) { 173 super.onCreate(savedInstanceState); 174 mLockPatternUtils = new LockPatternUtils(getActivity()); 175 Intent intent = getActivity().getIntent(); 176 if (!(getActivity() instanceof ChooseLockPassword)) { 177 throw new SecurityException("Fragment contained in wrong activity"); 178 } 179 mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, 180 mRequestedQuality), mLockPatternUtils.getRequestedPasswordQuality()); 181 mPasswordMinLength = Math.max(Math.max( 182 LockPatternUtils.MIN_LOCK_PASSWORD_SIZE, 183 intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)), 184 mLockPatternUtils.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 boolean wasSecureBefore = mLockPatternUtils.isSecure(); 435 mLockPatternUtils.clearLock(); 436 final boolean required = getActivity().getIntent().getBooleanExtra( 437 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 438 mLockPatternUtils.setCredentialRequiredToDecrypt(required); 439 mLockPatternUtils.saveLockPassword(pin, mRequestedQuality); 440 getActivity().setResult(RESULT_FINISHED); 441 getActivity().finish(); 442 mDone = true; 443 if (!wasSecureBefore) { 444 startActivity(getRedactionInterstitialIntent(getActivity())); 445 } 446 } else { 447 CharSequence tmp = mPasswordEntry.getText(); 448 if (tmp != null) { 449 Selection.setSelection((Spannable) tmp, 0, tmp.length()); 450 } 451 updateStage(Stage.ConfirmWrong); 452 } 453 } 454 if (errorMsg != null) { 455 showError(errorMsg, mUiStage); 456 } 457 } 458 459 protected void setNextEnabled(boolean enabled) { 460 mNextButton.setEnabled(enabled); 461 } 462 463 protected void setNextText(int text) { 464 mNextButton.setText(text); 465 } 466 467 public void onClick(View v) { 468 switch (v.getId()) { 469 case R.id.next_button: 470 handleNext(); 471 break; 472 473 case R.id.cancel_button: 474 getActivity().finish(); 475 break; 476 } 477 } 478 479 private void showError(String msg, final Stage next) { 480 mHeaderText.setText(msg); 481 mHeaderText.announceForAccessibility(mHeaderText.getText()); 482 Message mesg = mHandler.obtainMessage(MSG_SHOW_ERROR, next); 483 mHandler.removeMessages(MSG_SHOW_ERROR); 484 mHandler.sendMessageDelayed(mesg, ERROR_MESSAGE_TIMEOUT); 485 } 486 487 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 488 // Check if this was the result of hitting the enter or "done" key 489 if (actionId == EditorInfo.IME_NULL 490 || actionId == EditorInfo.IME_ACTION_DONE 491 || actionId == EditorInfo.IME_ACTION_NEXT) { 492 handleNext(); 493 return true; 494 } 495 return false; 496 } 497 498 /** 499 * Update the hint based on current Stage and length of password entry 500 */ 501 private void updateUi() { 502 String password = mPasswordEntry.getText().toString(); 503 final int length = password.length(); 504 if (mUiStage == Stage.Introduction && length > 0) { 505 if (length < mPasswordMinLength) { 506 String msg = getString(mIsAlphaMode ? R.string.lockpassword_password_too_short 507 : R.string.lockpassword_pin_too_short, mPasswordMinLength); 508 mHeaderText.setText(msg); 509 setNextEnabled(false); 510 } else { 511 String error = validatePassword(password); 512 if (error != null) { 513 mHeaderText.setText(error); 514 setNextEnabled(false); 515 } else { 516 mHeaderText.setText(R.string.lockpassword_press_continue); 517 setNextEnabled(true); 518 } 519 } 520 } else { 521 mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint); 522 setNextEnabled(length > 0); 523 } 524 setNextText(mUiStage.buttonText); 525 } 526 527 public void afterTextChanged(Editable s) { 528 // Changing the text while error displayed resets to NeedToConfirm state 529 if (mUiStage == Stage.ConfirmWrong) { 530 mUiStage = Stage.NeedToConfirm; 531 } 532 updateUi(); 533 } 534 535 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 536 537 } 538 539 public void onTextChanged(CharSequence s, int start, int before, int count) { 540 541 } 542 } 543} 544