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 android.app.Activity; 20 import android.app.Fragment; 21 import android.app.admin.DevicePolicyManager; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.inputmethodservice.KeyboardView; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.Message; 28 import android.os.UserHandle; 29 import android.text.Editable; 30 import android.text.InputType; 31 import android.text.Selection; 32 import android.text.Spannable; 33 import android.text.TextUtils; 34 import android.text.TextWatcher; 35 import android.util.Log; 36 import android.view.KeyEvent; 37 import android.view.LayoutInflater; 38 import android.view.View; 39 import android.view.View.OnClickListener; 40 import android.view.ViewGroup; 41 import android.view.inputmethod.EditorInfo; 42 import android.widget.Button; 43 import android.widget.TextView; 44 import android.widget.TextView.OnEditorActionListener; 45 46 import com.android.internal.logging.MetricsProto.MetricsEvent; 47 import com.android.internal.widget.LockPatternUtils; 48 import com.android.internal.widget.LockPatternUtils.RequestThrottledException; 49 import com.android.internal.widget.PasswordEntryKeyboardHelper; 50 import com.android.internal.widget.PasswordEntryKeyboardView; 51 import com.android.internal.widget.TextViewInputDisabler; 52 import com.android.settings.notification.RedactionInterstitial; 53 54 public class ChooseLockPassword extends SettingsActivity { 55 public static final String PASSWORD_MIN_KEY = "lockscreen.password_min"; 56 public static final String PASSWORD_MAX_KEY = "lockscreen.password_max"; 57 public static final String PASSWORD_MIN_LETTERS_KEY = "lockscreen.password_min_letters"; 58 public static final String PASSWORD_MIN_LOWERCASE_KEY = "lockscreen.password_min_lowercase"; 59 public static final String PASSWORD_MIN_UPPERCASE_KEY = "lockscreen.password_min_uppercase"; 60 public static final String PASSWORD_MIN_NUMERIC_KEY = "lockscreen.password_min_numeric"; 61 public static final String PASSWORD_MIN_SYMBOLS_KEY = "lockscreen.password_min_symbols"; 62 public static final String PASSWORD_MIN_NONLETTER_KEY = "lockscreen.password_min_nonletter"; 63 64 private static final String TAG = "ChooseLockPassword"; 65 66 @Override 67 public Intent getIntent() { 68 Intent modIntent = new Intent(super.getIntent()); 69 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName()); 70 return modIntent; 71 } 72 73 public static Intent createIntent(Context context, int quality, 74 int minLength, final int maxLength, boolean requirePasswordToDecrypt, 75 boolean confirmCredentials) { 76 Intent intent = new Intent().setClass(context, ChooseLockPassword.class); 77 intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality); 78 intent.putExtra(PASSWORD_MIN_KEY, minLength); 79 intent.putExtra(PASSWORD_MAX_KEY, maxLength); 80 intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials); 81 intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePasswordToDecrypt); 82 return intent; 83 } 84 85 public static Intent createIntent(Context context, int quality, 86 int minLength, final int maxLength, boolean requirePasswordToDecrypt, 87 boolean confirmCredentials, int userId) { 88 Intent intent = createIntent(context, quality, minLength, maxLength, 89 requirePasswordToDecrypt, confirmCredentials); 90 intent.putExtra(Intent.EXTRA_USER_ID, userId); 91 return intent; 92 } 93 94 public static Intent createIntent(Context context, int quality, 95 int minLength, final int maxLength, boolean requirePasswordToDecrypt, String password) { 96 Intent intent = createIntent(context, quality, minLength, maxLength, 97 requirePasswordToDecrypt, false); 98 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, password); 99 return intent; 100 } 101 102 public static Intent createIntent(Context context, int quality, int minLength, 103 int maxLength, boolean requirePasswordToDecrypt, String password, int userId) { 104 Intent intent = createIntent(context, quality, minLength, maxLength, 105 requirePasswordToDecrypt, password); 106 intent.putExtra(Intent.EXTRA_USER_ID, userId); 107 return intent; 108 } 109 110 public static Intent createIntent(Context context, int quality, 111 int minLength, final int maxLength, boolean requirePasswordToDecrypt, long challenge) { 112 Intent intent = createIntent(context, quality, minLength, maxLength, 113 requirePasswordToDecrypt, false); 114 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true); 115 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge); 116 return intent; 117 } 118 119 public static Intent createIntent(Context context, int quality, int minLength, 120 int maxLength, boolean requirePasswordToDecrypt, long challenge, int userId) { 121 Intent intent = createIntent(context, quality, minLength, maxLength, 122 requirePasswordToDecrypt, challenge); 123 intent.putExtra(Intent.EXTRA_USER_ID, userId); 124 return intent; 125 } 126 127 @Override 128 protected boolean isValidFragment(String fragmentName) { 129 if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true; 130 return false; 131 } 132 133 /* package */ Class<? extends Fragment> getFragmentClass() { 134 return ChooseLockPasswordFragment.class; 135 } 136 137 @Override 138 protected void onCreate(Bundle savedInstanceState) { 139 // TODO: Fix on phones 140 // Disable IME on our window since we provide our own keyboard 141 //getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 142 //WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 143 super.onCreate(savedInstanceState); 144 CharSequence msg = getText(R.string.lockpassword_choose_your_password_header); 145 setTitle(msg); 146 } 147 148 public static class ChooseLockPasswordFragment extends InstrumentedFragment 149 implements OnClickListener, OnEditorActionListener, TextWatcher, 150 SaveAndFinishWorker.Listener { 151 private static final String KEY_FIRST_PIN = "first_pin"; 152 private static final String KEY_UI_STAGE = "ui_stage"; 153 private static final String KEY_CURRENT_PASSWORD = "current_password"; 154 private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker"; 155 156 private String mCurrentPassword; 157 private String mChosenPassword; 158 private boolean mHasChallenge; 159 private long mChallenge; 160 private TextView mPasswordEntry; 161 private TextViewInputDisabler mPasswordEntryInputDisabler; 162 private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE; 163 private int mPasswordMaxLength = 16; 164 private int mPasswordMinLetters = 0; 165 private int mPasswordMinUpperCase = 0; 166 private int mPasswordMinLowerCase = 0; 167 private int mPasswordMinSymbols = 0; 168 private int mPasswordMinNumeric = 0; 169 private int mPasswordMinNonLetter = 0; 170 private LockPatternUtils mLockPatternUtils; 171 private SaveAndFinishWorker mSaveAndFinishWorker; 172 private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 173 private ChooseLockSettingsHelper mChooseLockSettingsHelper; 174 private Stage mUiStage = Stage.Introduction; 175 176 private TextView mHeaderText; 177 private String mFirstPin; 178 private KeyboardView mKeyboardView; 179 private PasswordEntryKeyboardHelper mKeyboardHelper; 180 private boolean mIsAlphaMode; 181 private Button mCancelButton; 182 private Button mNextButton; 183 private static final int CONFIRM_EXISTING_REQUEST = 58; 184 static final int RESULT_FINISHED = RESULT_FIRST_USER; 185 private static final long ERROR_MESSAGE_TIMEOUT = 3000; 186 private static final int MSG_SHOW_ERROR = 1; 187 188 private int mUserId; 189 private boolean mHideDrawer = false; 190 191 private Handler mHandler = new Handler() { 192 @Override 193 public void handleMessage(Message msg) { 194 if (msg.what == MSG_SHOW_ERROR) { 195 updateStage((Stage) msg.obj); 196 } 197 } 198 }; 199 200 /** 201 * Keep track internally of where the user is in choosing a pattern. 202 */ 203 protected enum Stage { 204 205 Introduction(R.string.lockpassword_choose_your_password_header, 206 R.string.lockpassword_choose_your_pin_header, 207 R.string.lockpassword_continue_label), 208 209 NeedToConfirm(R.string.lockpassword_confirm_your_password_header, 210 R.string.lockpassword_confirm_your_pin_header, 211 R.string.lockpassword_ok_label), 212 213 ConfirmWrong(R.string.lockpassword_confirm_passwords_dont_match, 214 R.string.lockpassword_confirm_pins_dont_match, 215 R.string.lockpassword_continue_label); 216 217 Stage(int hintInAlpha, int hintInNumeric, int nextButtonText) { 218 this.alphaHint = hintInAlpha; 219 this.numericHint = hintInNumeric; 220 this.buttonText = nextButtonText; 221 } 222 223 public final int alphaHint; 224 public final int numericHint; 225 public final int buttonText; 226 } 227 228 // required constructor for fragments 229 public ChooseLockPasswordFragment() { 230 231 } 232 233 @Override 234 public void onCreate(Bundle savedInstanceState) { 235 super.onCreate(savedInstanceState); 236 mLockPatternUtils = new LockPatternUtils(getActivity()); 237 Intent intent = getActivity().getIntent(); 238 if (!(getActivity() instanceof ChooseLockPassword)) { 239 throw new SecurityException("Fragment contained in wrong activity"); 240 } 241 // Only take this argument into account if it belongs to the current profile. 242 mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras()); 243 mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, 244 mRequestedQuality), mLockPatternUtils.getRequestedPasswordQuality( 245 mUserId)); 246 mPasswordMinLength = Math.max(Math.max( 247 LockPatternUtils.MIN_LOCK_PASSWORD_SIZE, 248 intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)), 249 mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId)); 250 mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength); 251 mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY, 252 mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters( 253 mUserId)); 254 mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY, 255 mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase( 256 mUserId)); 257 mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY, 258 mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase( 259 mUserId)); 260 mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY, 261 mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric( 262 mUserId)); 263 mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY, 264 mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols( 265 mUserId)); 266 mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY, 267 mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter( 268 mUserId)); 269 270 mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity()); 271 mHideDrawer = getActivity().getIntent().getBooleanExtra(EXTRA_HIDE_DRAWER, false); 272 273 if (intent.getBooleanExtra( 274 ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) { 275 SaveAndFinishWorker w = new SaveAndFinishWorker(); 276 final boolean required = getActivity().getIntent().getBooleanExtra( 277 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 278 String current = intent.getStringExtra( 279 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 280 w.setBlocking(true); 281 w.setListener(this); 282 w.start(mChooseLockSettingsHelper.utils(), required, 283 false, 0, current, current, mRequestedQuality, mUserId); 284 } 285 } 286 287 @Override 288 public View onCreateView(LayoutInflater inflater, ViewGroup container, 289 Bundle savedInstanceState) { 290 return inflater.inflate(R.layout.choose_lock_password, container, false); 291 } 292 293 @Override 294 public void onViewCreated(View view, Bundle savedInstanceState) { 295 super.onViewCreated(view, savedInstanceState); 296 297 mCancelButton = (Button) view.findViewById(R.id.cancel_button); 298 mCancelButton.setOnClickListener(this); 299 mNextButton = (Button) view.findViewById(R.id.next_button); 300 mNextButton.setOnClickListener(this); 301 302 mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality 303 || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality 304 || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality; 305 mKeyboardView = (PasswordEntryKeyboardView) view.findViewById(R.id.keyboard); 306 mPasswordEntry = (TextView) view.findViewById(R.id.password_entry); 307 mPasswordEntry.setOnEditorActionListener(this); 308 mPasswordEntry.addTextChangedListener(this); 309 mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry); 310 311 final Activity activity = getActivity(); 312 mKeyboardHelper = new PasswordEntryKeyboardHelper(activity, 313 mKeyboardView, mPasswordEntry); 314 mKeyboardHelper.setKeyboardMode(mIsAlphaMode ? 315 PasswordEntryKeyboardHelper.KEYBOARD_MODE_ALPHA 316 : PasswordEntryKeyboardHelper.KEYBOARD_MODE_NUMERIC); 317 318 mHeaderText = (TextView) view.findViewById(R.id.headerText); 319 mKeyboardView.requestFocus(); 320 321 int currentType = mPasswordEntry.getInputType(); 322 mPasswordEntry.setInputType(mIsAlphaMode ? currentType 323 : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)); 324 325 Intent intent = getActivity().getIntent(); 326 final boolean confirmCredentials = intent.getBooleanExtra( 327 ChooseLockGeneric.CONFIRM_CREDENTIALS, true); 328 mCurrentPassword = intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 329 mHasChallenge = intent.getBooleanExtra( 330 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 331 mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0); 332 if (savedInstanceState == null) { 333 updateStage(Stage.Introduction); 334 if (confirmCredentials) { 335 mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, 336 getString(R.string.unlock_set_unlock_launch_picker_title), true, 337 mUserId); 338 } 339 } else { 340 // restore from previous state 341 mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN); 342 final String state = savedInstanceState.getString(KEY_UI_STAGE); 343 if (state != null) { 344 mUiStage = Stage.valueOf(state); 345 updateStage(mUiStage); 346 } 347 348 if (mCurrentPassword == null) { 349 mCurrentPassword = savedInstanceState.getString(KEY_CURRENT_PASSWORD); 350 } 351 352 // Re-attach to the exiting worker if there is one. 353 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag( 354 FRAGMENT_TAG_SAVE_AND_FINISH); 355 } 356 if (activity instanceof SettingsActivity) { 357 final SettingsActivity sa = (SettingsActivity) activity; 358 int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header 359 : R.string.lockpassword_choose_your_pin_header; 360 CharSequence title = getText(id); 361 sa.setTitle(title); 362 } 363 } 364 365 @Override 366 protected int getMetricsCategory() { 367 return MetricsEvent.CHOOSE_LOCK_PASSWORD; 368 } 369 370 @Override 371 public void onResume() { 372 super.onResume(); 373 updateStage(mUiStage); 374 if (mSaveAndFinishWorker != null) { 375 mSaveAndFinishWorker.setListener(this); 376 } else { 377 mKeyboardView.requestFocus(); 378 } 379 } 380 381 @Override 382 public void onPause() { 383 mHandler.removeMessages(MSG_SHOW_ERROR); 384 if (mSaveAndFinishWorker != null) { 385 mSaveAndFinishWorker.setListener(null); 386 } 387 388 super.onPause(); 389 } 390 391 @Override 392 public void onSaveInstanceState(Bundle outState) { 393 super.onSaveInstanceState(outState); 394 outState.putString(KEY_UI_STAGE, mUiStage.name()); 395 outState.putString(KEY_FIRST_PIN, mFirstPin); 396 outState.putString(KEY_CURRENT_PASSWORD, mCurrentPassword); 397 } 398 399 @Override 400 public void onActivityResult(int requestCode, int resultCode, 401 Intent data) { 402 super.onActivityResult(requestCode, resultCode, data); 403 switch (requestCode) { 404 case CONFIRM_EXISTING_REQUEST: 405 if (resultCode != Activity.RESULT_OK) { 406 getActivity().setResult(RESULT_FINISHED); 407 getActivity().finish(); 408 } else { 409 mCurrentPassword = data.getStringExtra( 410 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 411 } 412 break; 413 } 414 } 415 416 protected Intent getRedactionInterstitialIntent(Context context) { 417 return RedactionInterstitial.createStartIntent(context, mUserId); 418 } 419 420 protected void updateStage(Stage stage) { 421 final Stage previousStage = mUiStage; 422 mUiStage = stage; 423 updateUi(); 424 425 // If the stage changed, announce the header for accessibility. This 426 // is a no-op when accessibility is disabled. 427 if (previousStage != stage) { 428 mHeaderText.announceForAccessibility(mHeaderText.getText()); 429 } 430 } 431 432 /** 433 * Validates PIN and returns a message to display if PIN fails test. 434 * @param password the raw password the user typed in 435 * @return error message to show to user or null if password is OK 436 */ 437 private String validatePassword(String password) { 438 if (password.length() < mPasswordMinLength) { 439 return getString(mIsAlphaMode ? 440 R.string.lockpassword_password_too_short 441 : R.string.lockpassword_pin_too_short, mPasswordMinLength); 442 } 443 if (password.length() > mPasswordMaxLength) { 444 return getString(mIsAlphaMode ? 445 R.string.lockpassword_password_too_long 446 : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1); 447 } 448 int letters = 0; 449 int numbers = 0; 450 int lowercase = 0; 451 int symbols = 0; 452 int uppercase = 0; 453 int nonletter = 0; 454 for (int i = 0; i < password.length(); i++) { 455 char c = password.charAt(i); 456 // allow non control Latin-1 characters only 457 if (c < 32 || c > 127) { 458 return getString(R.string.lockpassword_illegal_character); 459 } 460 if (c >= '0' && c <= '9') { 461 numbers++; 462 nonletter++; 463 } else if (c >= 'A' && c <= 'Z') { 464 letters++; 465 uppercase++; 466 } else if (c >= 'a' && c <= 'z') { 467 letters++; 468 lowercase++; 469 } else { 470 symbols++; 471 nonletter++; 472 } 473 } 474 if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC == mRequestedQuality 475 || DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality) { 476 if (letters > 0 || symbols > 0) { 477 // This shouldn't be possible unless user finds some way to bring up 478 // soft keyboard 479 return getString(R.string.lockpassword_pin_contains_non_digits); 480 } 481 // Check for repeated characters or sequences (e.g. '1234', '0000', '2468') 482 final int sequence = LockPatternUtils.maxLengthSequence(password); 483 if (DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX == mRequestedQuality 484 && sequence > LockPatternUtils.MAX_ALLOWED_SEQUENCE) { 485 return getString(R.string.lockpassword_pin_no_sequential_digits); 486 } 487 } else if (DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality) { 488 if (letters < mPasswordMinLetters) { 489 return String.format(getResources().getQuantityString( 490 R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters), 491 mPasswordMinLetters); 492 } else if (numbers < mPasswordMinNumeric) { 493 return String.format(getResources().getQuantityString( 494 R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric), 495 mPasswordMinNumeric); 496 } else if (lowercase < mPasswordMinLowerCase) { 497 return String.format(getResources().getQuantityString( 498 R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase), 499 mPasswordMinLowerCase); 500 } else if (uppercase < mPasswordMinUpperCase) { 501 return String.format(getResources().getQuantityString( 502 R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase), 503 mPasswordMinUpperCase); 504 } else if (symbols < mPasswordMinSymbols) { 505 return String.format(getResources().getQuantityString( 506 R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols), 507 mPasswordMinSymbols); 508 } else if (nonletter < mPasswordMinNonLetter) { 509 return String.format(getResources().getQuantityString( 510 R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter), 511 mPasswordMinNonLetter); 512 } 513 } else { 514 final boolean alphabetic = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC 515 == mRequestedQuality; 516 final boolean alphanumeric = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC 517 == mRequestedQuality; 518 if ((alphabetic || alphanumeric) && letters == 0) { 519 return getString(R.string.lockpassword_password_requires_alpha); 520 } 521 if (alphanumeric && numbers == 0) { 522 return getString(R.string.lockpassword_password_requires_digit); 523 } 524 } 525 if(mLockPatternUtils.checkPasswordHistory(password, mUserId)) { 526 return getString(mIsAlphaMode ? R.string.lockpassword_password_recently_used 527 : R.string.lockpassword_pin_recently_used); 528 } 529 530 return null; 531 } 532 533 public void handleNext() { 534 if (mSaveAndFinishWorker != null) return; 535 mChosenPassword = mPasswordEntry.getText().toString(); 536 if (TextUtils.isEmpty(mChosenPassword)) { 537 return; 538 } 539 String errorMsg = null; 540 if (mUiStage == Stage.Introduction) { 541 errorMsg = validatePassword(mChosenPassword); 542 if (errorMsg == null) { 543 mFirstPin = mChosenPassword; 544 mPasswordEntry.setText(""); 545 updateStage(Stage.NeedToConfirm); 546 } 547 } else if (mUiStage == Stage.NeedToConfirm) { 548 if (mFirstPin.equals(mChosenPassword)) { 549 startSaveAndFinish(); 550 } else { 551 CharSequence tmp = mPasswordEntry.getText(); 552 if (tmp != null) { 553 Selection.setSelection((Spannable) tmp, 0, tmp.length()); 554 } 555 updateStage(Stage.ConfirmWrong); 556 } 557 } 558 if (errorMsg != null) { 559 showError(errorMsg, mUiStage); 560 } 561 } 562 563 protected void setNextEnabled(boolean enabled) { 564 mNextButton.setEnabled(enabled); 565 } 566 567 protected void setNextText(int text) { 568 mNextButton.setText(text); 569 } 570 571 public void onClick(View v) { 572 switch (v.getId()) { 573 case R.id.next_button: 574 handleNext(); 575 break; 576 577 case R.id.cancel_button: 578 getActivity().finish(); 579 break; 580 } 581 } 582 583 private void showError(String msg, final Stage next) { 584 mHeaderText.setText(msg); 585 mHeaderText.announceForAccessibility(mHeaderText.getText()); 586 Message mesg = mHandler.obtainMessage(MSG_SHOW_ERROR, next); 587 mHandler.removeMessages(MSG_SHOW_ERROR); 588 mHandler.sendMessageDelayed(mesg, ERROR_MESSAGE_TIMEOUT); 589 } 590 591 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 592 // Check if this was the result of hitting the enter or "done" key 593 if (actionId == EditorInfo.IME_NULL 594 || actionId == EditorInfo.IME_ACTION_DONE 595 || actionId == EditorInfo.IME_ACTION_NEXT) { 596 handleNext(); 597 return true; 598 } 599 return false; 600 } 601 602 /** 603 * Update the hint based on current Stage and length of password entry 604 */ 605 private void updateUi() { 606 final boolean canInput = mSaveAndFinishWorker == null; 607 String password = mPasswordEntry.getText().toString(); 608 final int length = password.length(); 609 if (mUiStage == Stage.Introduction) { 610 if (length < mPasswordMinLength) { 611 String msg = getString(mIsAlphaMode ? R.string.lockpassword_password_too_short 612 : R.string.lockpassword_pin_too_short, mPasswordMinLength); 613 mHeaderText.setText(msg); 614 setNextEnabled(false); 615 } else { 616 String error = validatePassword(password); 617 if (error != null) { 618 mHeaderText.setText(error); 619 setNextEnabled(false); 620 } else { 621 mHeaderText.setText(null); 622 setNextEnabled(true); 623 } 624 } 625 } else { 626 mHeaderText.setText(mIsAlphaMode ? mUiStage.alphaHint : mUiStage.numericHint); 627 setNextEnabled(canInput && length > 0); 628 } 629 setNextText(mUiStage.buttonText); 630 mPasswordEntryInputDisabler.setInputEnabled(canInput); 631 } 632 633 public void afterTextChanged(Editable s) { 634 // Changing the text while error displayed resets to NeedToConfirm state 635 if (mUiStage == Stage.ConfirmWrong) { 636 mUiStage = Stage.NeedToConfirm; 637 } 638 updateUi(); 639 } 640 641 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 642 643 } 644 645 public void onTextChanged(CharSequence s, int start, int before, int count) { 646 647 } 648 649 private void startSaveAndFinish() { 650 if (mSaveAndFinishWorker != null) { 651 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker."); 652 return; 653 } 654 655 mPasswordEntryInputDisabler.setInputEnabled(false); 656 setNextEnabled(false); 657 658 mSaveAndFinishWorker = new SaveAndFinishWorker(); 659 mSaveAndFinishWorker.setListener(this); 660 661 getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, 662 FRAGMENT_TAG_SAVE_AND_FINISH).commit(); 663 getFragmentManager().executePendingTransactions(); 664 665 final boolean required = getActivity().getIntent().getBooleanExtra( 666 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 667 mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge, 668 mChosenPassword, mCurrentPassword, mRequestedQuality, mUserId); 669 } 670 671 @Override 672 public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { 673 getActivity().setResult(RESULT_FINISHED, resultData); 674 675 if (!wasSecureBefore) { 676 Intent intent = getRedactionInterstitialIntent(getActivity()); 677 if (intent != null) { 678 intent.putExtra(EXTRA_HIDE_DRAWER, mHideDrawer); 679 startActivity(intent); 680 } 681 } 682 getActivity().finish(); 683 } 684 } 685 686 private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase { 687 688 private String mChosenPassword; 689 private String mCurrentPassword; 690 private int mRequestedQuality; 691 692 public void start(LockPatternUtils utils, boolean required, 693 boolean hasChallenge, long challenge, 694 String chosenPassword, String currentPassword, int requestedQuality, int userId) { 695 prepare(utils, required, hasChallenge, challenge, userId); 696 697 mChosenPassword = chosenPassword; 698 mCurrentPassword = currentPassword; 699 mRequestedQuality = requestedQuality; 700 mUserId = userId; 701 702 start(); 703 } 704 705 @Override 706 protected Intent saveAndVerifyInBackground() { 707 Intent result = null; 708 mUtils.saveLockPassword(mChosenPassword, mCurrentPassword, mRequestedQuality, 709 mUserId); 710 711 if (mHasChallenge) { 712 byte[] token; 713 try { 714 token = mUtils.verifyPassword(mChosenPassword, mChallenge, mUserId); 715 } catch (RequestThrottledException e) { 716 token = null; 717 } 718 719 if (token == null) { 720 Log.e(TAG, "critical: no token returned for known good password."); 721 } 722 723 result = new Intent(); 724 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); 725 } 726 727 return result; 728 } 729 } 730 } 731