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 getIntent()67 public Intent getIntent() { 68 Intent modIntent = new Intent(super.getIntent()); 69 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName()); 70 return modIntent; 71 } 72 createIntent(Context context, int quality, int minLength, final int maxLength, boolean requirePasswordToDecrypt, boolean confirmCredentials)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 createIntent(Context context, int quality, int minLength, final int maxLength, boolean requirePasswordToDecrypt, boolean confirmCredentials, int userId)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 createIntent(Context context, int quality, int minLength, final int maxLength, boolean requirePasswordToDecrypt, String password)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 createIntent(Context context, int quality, int minLength, int maxLength, boolean requirePasswordToDecrypt, String password, int userId)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 createIntent(Context context, int quality, int minLength, final int maxLength, boolean requirePasswordToDecrypt, long challenge)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 createIntent(Context context, int quality, int minLength, int maxLength, boolean requirePasswordToDecrypt, long challenge, int userId)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 isValidFragment(String fragmentName)128 protected boolean isValidFragment(String fragmentName) { 129 if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true; 130 return false; 131 } 132 getFragmentClass()133 /* package */ Class<? extends Fragment> getFragmentClass() { 134 return ChooseLockPasswordFragment.class; 135 } 136 137 @Override onCreate(Bundle savedInstanceState)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 Stage(int hintInAlpha, int hintInNumeric, int nextButtonText)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 ChooseLockPasswordFragment()229 public ChooseLockPasswordFragment() { 230 231 } 232 233 @Override onCreate(Bundle savedInstanceState)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 onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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 onViewCreated(View view, Bundle savedInstanceState)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 getMetricsCategory()366 protected int getMetricsCategory() { 367 return MetricsEvent.CHOOSE_LOCK_PASSWORD; 368 } 369 370 @Override onResume()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 onPause()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 onSaveInstanceState(Bundle outState)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 onActivityResult(int requestCode, int resultCode, Intent data)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 getRedactionInterstitialIntent(Context context)416 protected Intent getRedactionInterstitialIntent(Context context) { 417 return RedactionInterstitial.createStartIntent(context, mUserId); 418 } 419 updateStage(Stage stage)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 */ validatePassword(String password)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 handleNext()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 setNextEnabled(boolean enabled)563 protected void setNextEnabled(boolean enabled) { 564 mNextButton.setEnabled(enabled); 565 } 566 setNextText(int text)567 protected void setNextText(int text) { 568 mNextButton.setText(text); 569 } 570 onClick(View v)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 showError(String msg, final Stage next)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 onEditorAction(TextView v, int actionId, KeyEvent event)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 */ updateUi()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 afterTextChanged(Editable s)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 beforeTextChanged(CharSequence s, int start, int count, int after)641 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 642 643 } 644 onTextChanged(CharSequence s, int start, int before, int count)645 public void onTextChanged(CharSequence s, int start, int before, int count) { 646 647 } 648 startSaveAndFinish()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 onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)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 start(LockPatternUtils utils, boolean required, boolean hasChallenge, long challenge, String chosenPassword, String currentPassword, int requestedQuality, int userId)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 saveAndVerifyInBackground()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