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