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