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