ChooseLockPassword.java revision 9990f397220703f4d2c922560a8e29e60bcce39f
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.password; 18 19import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; 20import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; 21import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; 22import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 23import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; 24 25import android.app.Activity; 26import android.app.Fragment; 27import android.app.admin.DevicePolicyManager; 28import android.app.admin.PasswordMetrics; 29import android.content.Context; 30import android.content.Intent; 31import android.content.res.Resources.Theme; 32import android.graphics.Insets; 33import android.os.Bundle; 34import android.os.Handler; 35import android.os.Message; 36import android.support.annotation.StringRes; 37import android.support.v7.widget.LinearLayoutManager; 38import android.support.v7.widget.RecyclerView; 39import android.text.Editable; 40import android.text.InputType; 41import android.text.Selection; 42import android.text.Spannable; 43import android.text.TextUtils; 44import android.text.TextWatcher; 45import android.util.Log; 46import android.view.KeyEvent; 47import android.view.LayoutInflater; 48import android.view.View; 49import android.view.View.OnClickListener; 50import android.view.ViewGroup; 51import android.view.inputmethod.EditorInfo; 52import android.widget.Button; 53import android.widget.EditText; 54import android.widget.LinearLayout; 55import android.widget.TextView; 56import android.widget.TextView.OnEditorActionListener; 57 58import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 59import com.android.internal.widget.LockPatternUtils; 60import com.android.internal.widget.LockPatternUtils.RequestThrottledException; 61import com.android.internal.widget.TextViewInputDisabler; 62import com.android.settings.EncryptionInterstitial; 63import com.android.settings.R; 64import com.android.settings.SettingsActivity; 65import com.android.settings.SetupWizardUtils; 66import com.android.settings.Utils; 67import com.android.settings.core.InstrumentedPreferenceFragment; 68import com.android.settings.notification.RedactionInterstitial; 69import com.android.setupwizardlib.GlifLayout; 70 71import java.util.ArrayList; 72import java.util.List; 73 74public class ChooseLockPassword extends SettingsActivity { 75 public static final String PASSWORD_MIN_KEY = "lockscreen.password_min"; 76 public static final String PASSWORD_MAX_KEY = "lockscreen.password_max"; 77 public static final String PASSWORD_MIN_LETTERS_KEY = "lockscreen.password_min_letters"; 78 public static final String PASSWORD_MIN_LOWERCASE_KEY = "lockscreen.password_min_lowercase"; 79 public static final String PASSWORD_MIN_UPPERCASE_KEY = "lockscreen.password_min_uppercase"; 80 public static final String PASSWORD_MIN_NUMERIC_KEY = "lockscreen.password_min_numeric"; 81 public static final String PASSWORD_MIN_SYMBOLS_KEY = "lockscreen.password_min_symbols"; 82 public static final String PASSWORD_MIN_NONLETTER_KEY = "lockscreen.password_min_nonletter"; 83 84 private static final String TAG = "ChooseLockPassword"; 85 86 @Override 87 public Intent getIntent() { 88 Intent modIntent = new Intent(super.getIntent()); 89 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName()); 90 return modIntent; 91 } 92 93 @Override 94 protected void onApplyThemeResource(Theme theme, int resid, boolean first) { 95 resid = SetupWizardUtils.getTheme(getIntent()); 96 super.onApplyThemeResource(theme, resid, first); 97 } 98 99 public static class IntentBuilder { 100 101 private final Intent mIntent; 102 103 public IntentBuilder(Context context) { 104 mIntent = new Intent(context, ChooseLockPassword.class); 105 mIntent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, false); 106 mIntent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, false); 107 } 108 109 public IntentBuilder setPasswordQuality(int quality) { 110 mIntent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality); 111 return this; 112 } 113 114 public IntentBuilder setPasswordLengthRange(int min, int max) { 115 mIntent.putExtra(PASSWORD_MIN_KEY, min); 116 mIntent.putExtra(PASSWORD_MAX_KEY, max); 117 return this; 118 } 119 120 public IntentBuilder setUserId(int userId) { 121 mIntent.putExtra(Intent.EXTRA_USER_ID, userId); 122 return this; 123 } 124 125 public IntentBuilder setChallenge(long challenge) { 126 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true); 127 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge); 128 return this; 129 } 130 131 public IntentBuilder setPassword(String password) { 132 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 133 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, password); 134 return this; 135 } 136 137 public IntentBuilder setForFingerprint(boolean forFingerprint) { 138 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, forFingerprint); 139 return this; 140 } 141 142 public Intent build() { 143 return mIntent; 144 } 145 } 146 147 @Override 148 protected boolean isValidFragment(String fragmentName) { 149 if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true; 150 return false; 151 } 152 153 /* package */ Class<? extends Fragment> getFragmentClass() { 154 return ChooseLockPasswordFragment.class; 155 } 156 157 @Override 158 protected void onCreate(Bundle savedInstanceState) { 159 super.onCreate(savedInstanceState); 160 boolean forFingerprint = getIntent() 161 .getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false); 162 CharSequence msg = getText(forFingerprint 163 ? R.string.lockpassword_choose_your_password_header_for_fingerprint 164 : R.string.lockpassword_choose_your_password_header); 165 setTitle(msg); 166 LinearLayout layout = (LinearLayout) findViewById(R.id.content_parent); 167 layout.setFitsSystemWindows(false); 168 } 169 170 public static class ChooseLockPasswordFragment extends InstrumentedPreferenceFragment 171 implements OnClickListener, OnEditorActionListener, TextWatcher, 172 SaveAndFinishWorker.Listener { 173 private static final String KEY_FIRST_PIN = "first_pin"; 174 private static final String KEY_UI_STAGE = "ui_stage"; 175 private static final String KEY_CURRENT_PASSWORD = "current_password"; 176 private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker"; 177 178 private String mCurrentPassword; 179 private String mChosenPassword; 180 private boolean mHasChallenge; 181 private long mChallenge; 182 private EditText mPasswordEntry; 183 private TextViewInputDisabler mPasswordEntryInputDisabler; 184 private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE; 185 private int mPasswordMaxLength = 16; 186 private int mPasswordMinLetters = 0; 187 private int mPasswordMinUpperCase = 0; 188 private int mPasswordMinLowerCase = 0; 189 private int mPasswordMinSymbols = 0; 190 private int mPasswordMinNumeric = 0; 191 private int mPasswordMinNonLetter = 0; 192 private int mPasswordMinLengthToFulfillAllPolicies = 0; 193 protected int mUserId; 194 private boolean mHideDrawer = false; 195 /** 196 * Password requirements that we need to verify. 197 */ 198 private int[] mPasswordRequirements; 199 200 private LockPatternUtils mLockPatternUtils; 201 private SaveAndFinishWorker mSaveAndFinishWorker; 202 private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 203 private ChooseLockSettingsHelper mChooseLockSettingsHelper; 204 private Stage mUiStage = Stage.Introduction; 205 private PasswordRequirementAdapter mPasswordRequirementAdapter; 206 private GlifLayout mLayout; 207 private boolean mForFingerprint; 208 209 private String mFirstPin; 210 private RecyclerView mPasswordRestrictionView; 211 protected boolean mIsAlphaMode; 212 protected Button mCancelButton; 213 private Button mNextButton; 214 215 private TextChangedHandler mTextChangedHandler; 216 217 private static final int CONFIRM_EXISTING_REQUEST = 58; 218 static final int RESULT_FINISHED = RESULT_FIRST_USER; 219 220 private static final int MIN_LETTER_IN_PASSWORD = 0; 221 private static final int MIN_UPPER_LETTERS_IN_PASSWORD = 1; 222 private static final int MIN_LOWER_LETTERS_IN_PASSWORD = 2; 223 private static final int MIN_SYMBOLS_IN_PASSWORD = 3; 224 private static final int MIN_NUMBER_IN_PASSWORD = 4; 225 private static final int MIN_NON_LETTER_IN_PASSWORD = 5; 226 227 // Error code returned from {@link #validatePassword(String)}. 228 private static final int NO_ERROR = 0; 229 private static final int CONTAIN_INVALID_CHARACTERS = 1 << 0; 230 private static final int TOO_SHORT = 1 << 1; 231 private static final int TOO_LONG = 1 << 2; 232 private static final int CONTAIN_NON_DIGITS = 1 << 3; 233 private static final int CONTAIN_SEQUENTIAL_DIGITS = 1 << 4; 234 private static final int RECENTLY_USED = 1 << 5; 235 private static final int NOT_ENOUGH_LETTER = 1 << 6; 236 private static final int NOT_ENOUGH_UPPER_CASE = 1 << 7; 237 private static final int NOT_ENOUGH_LOWER_CASE = 1 << 8; 238 private static final int NOT_ENOUGH_DIGITS = 1 << 9; 239 private static final int NOT_ENOUGH_SYMBOLS = 1 << 10; 240 private static final int NOT_ENOUGH_NON_LETTER = 1 << 11; 241 242 /** 243 * Keep track internally of where the user is in choosing a pattern. 244 */ 245 protected enum Stage { 246 247 Introduction( 248 R.string.lockpassword_choose_your_password_header, 249 R.string.lockpassword_choose_your_password_header_for_fingerprint, 250 R.string.lockpassword_choose_your_pin_header, 251 R.string.lockpassword_choose_your_pin_header_for_fingerprint, 252 R.string.next_label), 253 254 NeedToConfirm( 255 R.string.lockpassword_confirm_your_password_header, 256 R.string.lockpassword_confirm_your_password_header, 257 R.string.lockpassword_confirm_your_pin_header, 258 R.string.lockpassword_confirm_your_pin_header, 259 R.string.lockpassword_ok_label), 260 261 ConfirmWrong( 262 R.string.lockpassword_confirm_passwords_dont_match, 263 R.string.lockpassword_confirm_passwords_dont_match, 264 R.string.lockpassword_confirm_pins_dont_match, 265 R.string.lockpassword_confirm_pins_dont_match, 266 R.string.next_label); 267 268 Stage(int hintInAlpha, int hintInAlphaForFingerprint, 269 int hintInNumeric, int hintInNumericForFingerprint, int nextButtonText) { 270 this.alphaHint = hintInAlpha; 271 this.alphaHintForFingerprint = hintInAlphaForFingerprint; 272 this.numericHint = hintInNumeric; 273 this.numericHintForFingerprint = hintInNumericForFingerprint; 274 this.buttonText = nextButtonText; 275 } 276 277 public final int alphaHint; 278 public final int alphaHintForFingerprint; 279 public final int numericHint; 280 public final int numericHintForFingerprint; 281 public final int buttonText; 282 283 public @StringRes int getHint(boolean isAlpha, boolean isFingerprint) { 284 if (isAlpha) { 285 return isFingerprint ? alphaHintForFingerprint : alphaHint; 286 } else { 287 return isFingerprint ? numericHintForFingerprint : numericHint; 288 } 289 } 290 } 291 292 // required constructor for fragments 293 public ChooseLockPasswordFragment() { 294 295 } 296 297 @Override 298 public void onCreate(Bundle savedInstanceState) { 299 super.onCreate(savedInstanceState); 300 mLockPatternUtils = new LockPatternUtils(getActivity()); 301 Intent intent = getActivity().getIntent(); 302 if (!(getActivity() instanceof ChooseLockPassword)) { 303 throw new SecurityException("Fragment contained in wrong activity"); 304 } 305 // Only take this argument into account if it belongs to the current profile. 306 mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras()); 307 mForFingerprint = intent.getBooleanExtra( 308 ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false); 309 processPasswordRequirements(intent); 310 mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity()); 311 mHideDrawer = getActivity().getIntent().getBooleanExtra(EXTRA_HIDE_DRAWER, false); 312 313 if (intent.getBooleanExtra( 314 ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) { 315 SaveAndFinishWorker w = new SaveAndFinishWorker(); 316 final boolean required = getActivity().getIntent().getBooleanExtra( 317 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 318 String current = intent.getStringExtra( 319 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 320 w.setBlocking(true); 321 w.setListener(this); 322 w.start(mChooseLockSettingsHelper.utils(), required, 323 false, 0, current, current, mRequestedQuality, mUserId); 324 } 325 mTextChangedHandler = new TextChangedHandler(); 326 } 327 328 @Override 329 public View onCreateView(LayoutInflater inflater, ViewGroup container, 330 Bundle savedInstanceState) { 331 return inflater.inflate(R.layout.choose_lock_password, container, false); 332 } 333 334 @Override 335 public void onViewCreated(View view, Bundle savedInstanceState) { 336 super.onViewCreated(view, savedInstanceState); 337 338 mLayout = (GlifLayout) view; 339 340 // Make the password container consume the optical insets so the edit text is aligned 341 // with the sides of the parent visually. 342 ViewGroup container = view.findViewById(R.id.password_container); 343 container.setOpticalInsets(Insets.NONE); 344 345 mCancelButton = (Button) view.findViewById(R.id.cancel_button); 346 mCancelButton.setOnClickListener(this); 347 mNextButton = (Button) view.findViewById(R.id.next_button); 348 mNextButton.setOnClickListener(this); 349 350 if (mForFingerprint) { 351 TextView fingerprintBackupMessage = 352 view.findViewById(R.id.fingerprint_backup_message); 353 if (fingerprintBackupMessage != null) { 354 fingerprintBackupMessage.setVisibility(View.VISIBLE); 355 fingerprintBackupMessage 356 .setText(R.string.setup_lock_settings_picker_fingerprint_message); 357 } 358 } 359 360 mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality 361 || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality 362 || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality; 363 364 setupPasswordRequirementsView(view); 365 366 mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); 367 mPasswordEntry = (EditText) view.findViewById(R.id.password_entry); 368 mPasswordEntry.setOnEditorActionListener(this); 369 mPasswordEntry.addTextChangedListener(this); 370 mPasswordEntry.requestFocus(); 371 mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry); 372 373 final Activity activity = getActivity(); 374 375 int currentType = mPasswordEntry.getInputType(); 376 mPasswordEntry.setInputType(mIsAlphaMode ? currentType 377 : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)); 378 379 Intent intent = getActivity().getIntent(); 380 final boolean confirmCredentials = intent.getBooleanExtra( 381 ChooseLockGeneric.CONFIRM_CREDENTIALS, true); 382 mCurrentPassword = intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 383 mHasChallenge = intent.getBooleanExtra( 384 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 385 mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0); 386 if (savedInstanceState == null) { 387 updateStage(Stage.Introduction); 388 if (confirmCredentials) { 389 mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, 390 getString(R.string.unlock_set_unlock_launch_picker_title), true, 391 mUserId); 392 } 393 } else { 394 // restore from previous state 395 mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN); 396 final String state = savedInstanceState.getString(KEY_UI_STAGE); 397 if (state != null) { 398 mUiStage = Stage.valueOf(state); 399 updateStage(mUiStage); 400 } 401 402 if (mCurrentPassword == null) { 403 mCurrentPassword = savedInstanceState.getString(KEY_CURRENT_PASSWORD); 404 } 405 406 // Re-attach to the exiting worker if there is one. 407 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag( 408 FRAGMENT_TAG_SAVE_AND_FINISH); 409 } 410 411 if (activity instanceof SettingsActivity) { 412 final SettingsActivity sa = (SettingsActivity) activity; 413 int title = Stage.Introduction.getHint(mIsAlphaMode, mForFingerprint); 414 sa.setTitle(title); 415 mLayout.setHeaderText(title); 416 } 417 } 418 419 private void setupPasswordRequirementsView(View view) { 420 // Construct passwordRequirements and requirementDescriptions. 421 List<Integer> passwordRequirements = new ArrayList<>(); 422 List<String> requirementDescriptions = new ArrayList<>(); 423 if (mPasswordMinUpperCase > 0) { 424 passwordRequirements.add(MIN_UPPER_LETTERS_IN_PASSWORD); 425 requirementDescriptions.add(getResources().getQuantityString( 426 R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase, 427 mPasswordMinUpperCase)); 428 } 429 if (mPasswordMinLowerCase > 0) { 430 passwordRequirements.add(MIN_LOWER_LETTERS_IN_PASSWORD); 431 requirementDescriptions.add(getResources().getQuantityString( 432 R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase, 433 mPasswordMinLowerCase)); 434 } 435 if (mPasswordMinLetters > 0) { 436 if (mPasswordMinLetters > mPasswordMinUpperCase + mPasswordMinLowerCase) { 437 passwordRequirements.add(MIN_LETTER_IN_PASSWORD); 438 requirementDescriptions.add(getResources().getQuantityString( 439 R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters, 440 mPasswordMinLetters)); 441 } 442 } 443 if (mPasswordMinNumeric > 0) { 444 passwordRequirements.add(MIN_NUMBER_IN_PASSWORD); 445 requirementDescriptions.add(getResources().getQuantityString( 446 R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric, 447 mPasswordMinNumeric)); 448 } 449 if (mPasswordMinSymbols > 0) { 450 passwordRequirements.add(MIN_SYMBOLS_IN_PASSWORD); 451 requirementDescriptions.add(getResources().getQuantityString( 452 R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols, 453 mPasswordMinSymbols)); 454 } 455 if (mPasswordMinNonLetter > 0) { 456 if (mPasswordMinNonLetter > mPasswordMinNumeric + mPasswordMinSymbols) { 457 passwordRequirements.add(MIN_NON_LETTER_IN_PASSWORD); 458 requirementDescriptions.add(getResources().getQuantityString( 459 R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter, 460 461 mPasswordMinNonLetter)); 462 } 463 } 464 // Convert list to array. 465 mPasswordRequirements = passwordRequirements.stream().mapToInt(i -> i).toArray(); 466 mPasswordRestrictionView = 467 (RecyclerView) view.findViewById(R.id.password_requirements_view); 468 mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); 469 mPasswordRequirementAdapter = new PasswordRequirementAdapter(); 470 mPasswordRestrictionView.setAdapter(mPasswordRequirementAdapter); 471 } 472 473 @Override 474 public int getMetricsCategory() { 475 return MetricsEvent.CHOOSE_LOCK_PASSWORD; 476 } 477 478 @Override 479 public void onResume() { 480 super.onResume(); 481 updateStage(mUiStage); 482 if (mSaveAndFinishWorker != null) { 483 mSaveAndFinishWorker.setListener(this); 484 } else { 485 mPasswordEntry.requestFocus(); 486 } 487 } 488 489 @Override 490 public void onPause() { 491 if (mSaveAndFinishWorker != null) { 492 mSaveAndFinishWorker.setListener(null); 493 } 494 super.onPause(); 495 } 496 497 @Override 498 public void onSaveInstanceState(Bundle outState) { 499 super.onSaveInstanceState(outState); 500 outState.putString(KEY_UI_STAGE, mUiStage.name()); 501 outState.putString(KEY_FIRST_PIN, mFirstPin); 502 outState.putString(KEY_CURRENT_PASSWORD, mCurrentPassword); 503 } 504 505 @Override 506 public void onActivityResult(int requestCode, int resultCode, 507 Intent data) { 508 super.onActivityResult(requestCode, resultCode, data); 509 switch (requestCode) { 510 case CONFIRM_EXISTING_REQUEST: 511 if (resultCode != Activity.RESULT_OK) { 512 getActivity().setResult(RESULT_FINISHED); 513 getActivity().finish(); 514 } else { 515 mCurrentPassword = data.getStringExtra( 516 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 517 } 518 break; 519 } 520 } 521 522 protected Intent getRedactionInterstitialIntent(Context context) { 523 return RedactionInterstitial.createStartIntent(context, mUserId); 524 } 525 526 protected void updateStage(Stage stage) { 527 final Stage previousStage = mUiStage; 528 mUiStage = stage; 529 updateUi(); 530 531 // If the stage changed, announce the header for accessibility. This 532 // is a no-op when accessibility is disabled. 533 if (previousStage != stage) { 534 mLayout.announceForAccessibility(mLayout.getHeaderText()); 535 } 536 } 537 538 /** 539 * Read the requirements from {@link DevicePolicyManager} and intent and aggregate them. 540 * 541 * @param intent the incoming intent 542 */ 543 private void processPasswordRequirements(Intent intent) { 544 final int dpmPasswordQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId); 545 mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, 546 mRequestedQuality), dpmPasswordQuality); 547 mPasswordMinLength = Math.max(Math.max( 548 LockPatternUtils.MIN_LOCK_PASSWORD_SIZE, 549 intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)), 550 mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId)); 551 mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength); 552 mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY, 553 mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters( 554 mUserId)); 555 mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY, 556 mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase( 557 mUserId)); 558 mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY, 559 mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase( 560 mUserId)); 561 mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY, 562 mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric( 563 mUserId)); 564 mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY, 565 mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols( 566 mUserId)); 567 mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY, 568 mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter( 569 mUserId)); 570 571 // Modify the value based on dpm policy. 572 switch (dpmPasswordQuality) { 573 case PASSWORD_QUALITY_ALPHABETIC: 574 if (mPasswordMinLetters == 0) { 575 mPasswordMinLetters = 1; 576 } 577 break; 578 case PASSWORD_QUALITY_ALPHANUMERIC: 579 if (mPasswordMinLetters == 0) { 580 mPasswordMinLetters = 1; 581 } 582 if (mPasswordMinNumeric == 0) { 583 mPasswordMinNumeric = 1; 584 } 585 break; 586 case PASSWORD_QUALITY_COMPLEX: 587 // Reserve all the requirements. 588 break; 589 default: 590 mPasswordMinNumeric = 0; 591 mPasswordMinLetters = 0; 592 mPasswordMinUpperCase = 0; 593 mPasswordMinLowerCase = 0; 594 mPasswordMinSymbols = 0; 595 mPasswordMinNonLetter = 0; 596 } 597 mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies(); 598 } 599 600 /** 601 * Validates PIN and returns the validation result. 602 * 603 * @param password the raw password the user typed in 604 * @return the validation result. 605 */ 606 private int validatePassword(String password) { 607 int errorCode = NO_ERROR; 608 609 if (password.length() < mPasswordMinLength) { 610 if (mPasswordMinLength > mPasswordMinLengthToFulfillAllPolicies) { 611 errorCode |= TOO_SHORT; 612 } 613 } else if (password.length() > mPasswordMaxLength) { 614 errorCode |= TOO_LONG; 615 } else { 616 // The length requirements are fulfilled. 617 if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) { 618 // Check for repeated characters or sequences (e.g. '1234', '0000', '2468') 619 final int sequence = PasswordMetrics.maxLengthSequence(password); 620 if (sequence > PasswordMetrics.MAX_ALLOWED_SEQUENCE) { 621 errorCode |= CONTAIN_SEQUENTIAL_DIGITS; 622 } 623 } 624 // Is the password recently used? 625 if (mLockPatternUtils.checkPasswordHistory(password, mUserId)) { 626 errorCode |= RECENTLY_USED; 627 } 628 } 629 630 // Allow non-control Latin-1 characters only. 631 for (int i = 0; i < password.length(); i++) { 632 char c = password.charAt(i); 633 if (c < 32 || c > 127) { 634 errorCode |= CONTAIN_INVALID_CHARACTERS; 635 break; 636 } 637 } 638 639 final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password); 640 641 // Ensure no non-digits if we are requesting numbers. This shouldn't be possible unless 642 // user finds some way to bring up soft keyboard. 643 if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC 644 || mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) { 645 if (metrics.letters > 0 || metrics.symbols > 0) { 646 errorCode |= CONTAIN_NON_DIGITS; 647 } 648 } 649 650 // Check the requirements one by one. 651 for (int i = 0; i < mPasswordRequirements.length; i++) { 652 int passwordRestriction = mPasswordRequirements[i]; 653 switch (passwordRestriction) { 654 case MIN_LETTER_IN_PASSWORD: 655 if (metrics.letters < mPasswordMinLetters) { 656 errorCode |= NOT_ENOUGH_LETTER; 657 } 658 break; 659 case MIN_UPPER_LETTERS_IN_PASSWORD: 660 if (metrics.upperCase < mPasswordMinUpperCase) { 661 errorCode |= NOT_ENOUGH_UPPER_CASE; 662 } 663 break; 664 case MIN_LOWER_LETTERS_IN_PASSWORD: 665 if (metrics.lowerCase < mPasswordMinLowerCase) { 666 errorCode |= NOT_ENOUGH_LOWER_CASE; 667 } 668 break; 669 case MIN_SYMBOLS_IN_PASSWORD: 670 if (metrics.symbols < mPasswordMinSymbols) { 671 errorCode |= NOT_ENOUGH_SYMBOLS; 672 } 673 break; 674 case MIN_NUMBER_IN_PASSWORD: 675 if (metrics.numeric < mPasswordMinNumeric) { 676 errorCode |= NOT_ENOUGH_DIGITS; 677 } 678 break; 679 case MIN_NON_LETTER_IN_PASSWORD: 680 if (metrics.nonLetter < mPasswordMinNonLetter) { 681 errorCode |= NOT_ENOUGH_NON_LETTER; 682 } 683 break; 684 } 685 } 686 return errorCode; 687 } 688 689 public void handleNext() { 690 if (mSaveAndFinishWorker != null) return; 691 mChosenPassword = mPasswordEntry.getText().toString(); 692 if (TextUtils.isEmpty(mChosenPassword)) { 693 return; 694 } 695 if (mUiStage == Stage.Introduction) { 696 if (validatePassword(mChosenPassword) == NO_ERROR) { 697 mFirstPin = mChosenPassword; 698 mPasswordEntry.setText(""); 699 updateStage(Stage.NeedToConfirm); 700 } 701 } else if (mUiStage == Stage.NeedToConfirm) { 702 if (mFirstPin.equals(mChosenPassword)) { 703 startSaveAndFinish(); 704 } else { 705 CharSequence tmp = mPasswordEntry.getText(); 706 if (tmp != null) { 707 Selection.setSelection((Spannable) tmp, 0, tmp.length()); 708 } 709 updateStage(Stage.ConfirmWrong); 710 } 711 } 712 } 713 714 protected void setNextEnabled(boolean enabled) { 715 mNextButton.setEnabled(enabled); 716 } 717 718 protected void setNextText(int text) { 719 mNextButton.setText(text); 720 } 721 722 public void onClick(View v) { 723 switch (v.getId()) { 724 case R.id.next_button: 725 handleNext(); 726 break; 727 728 case R.id.cancel_button: 729 getActivity().finish(); 730 break; 731 } 732 } 733 734 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 735 // Check if this was the result of hitting the enter or "done" key 736 if (actionId == EditorInfo.IME_NULL 737 || actionId == EditorInfo.IME_ACTION_DONE 738 || actionId == EditorInfo.IME_ACTION_NEXT) { 739 handleNext(); 740 return true; 741 } 742 return false; 743 } 744 745 /** 746 * @param errorCode error code returned from {@link #validatePassword(String)}. 747 * @return an array of messages describing the error, important messages come first. 748 */ 749 private String[] convertErrorCodeToMessages(int errorCode) { 750 List<String> messages = new ArrayList<>(); 751 if ((errorCode & CONTAIN_INVALID_CHARACTERS) > 0) { 752 messages.add(getString(R.string.lockpassword_illegal_character)); 753 } 754 if ((errorCode & CONTAIN_NON_DIGITS) > 0) { 755 messages.add(getString(R.string.lockpassword_pin_contains_non_digits)); 756 } 757 if ((errorCode & NOT_ENOUGH_UPPER_CASE) > 0) { 758 messages.add(getResources().getQuantityString( 759 R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase, 760 mPasswordMinUpperCase)); 761 } 762 if ((errorCode & NOT_ENOUGH_LOWER_CASE) > 0) { 763 messages.add(getResources().getQuantityString( 764 R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase, 765 mPasswordMinLowerCase)); 766 } 767 if ((errorCode & NOT_ENOUGH_LETTER) > 0) { 768 messages.add(getResources().getQuantityString( 769 R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters, 770 mPasswordMinLetters)); 771 } 772 if ((errorCode & NOT_ENOUGH_DIGITS) > 0) { 773 messages.add(getResources().getQuantityString( 774 R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric, 775 mPasswordMinNumeric)); 776 } 777 if ((errorCode & NOT_ENOUGH_SYMBOLS) > 0) { 778 messages.add(getResources().getQuantityString( 779 R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols, 780 mPasswordMinSymbols)); 781 } 782 if ((errorCode & NOT_ENOUGH_NON_LETTER) > 0) { 783 messages.add(getResources().getQuantityString( 784 R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter, 785 mPasswordMinNonLetter)); 786 } 787 if ((errorCode & TOO_SHORT) > 0) { 788 messages.add(getString(mIsAlphaMode ? 789 R.string.lockpassword_password_too_short 790 : R.string.lockpassword_pin_too_short, mPasswordMinLength)); 791 } 792 if ((errorCode & TOO_LONG) > 0) { 793 messages.add(getString(mIsAlphaMode ? 794 R.string.lockpassword_password_too_long 795 : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1)); 796 } 797 if ((errorCode & CONTAIN_SEQUENTIAL_DIGITS) > 0) { 798 messages.add(getString(R.string.lockpassword_pin_no_sequential_digits)); 799 } 800 if ((errorCode & RECENTLY_USED) > 0) { 801 messages.add(getString((mIsAlphaMode) ? R.string.lockpassword_password_recently_used 802 : R.string.lockpassword_pin_recently_used)); 803 } 804 return messages.toArray(new String[0]); 805 } 806 807 private int getMinLengthToFulfillAllPolicies() { 808 final int minLengthForLetters = Math.max(mPasswordMinLetters, 809 mPasswordMinUpperCase + mPasswordMinLowerCase); 810 final int minLengthForNonLetters = Math.max(mPasswordMinNonLetter, 811 mPasswordMinSymbols + mPasswordMinNumeric); 812 return minLengthForLetters + minLengthForNonLetters; 813 } 814 815 /** 816 * Update the hint based on current Stage and length of password entry 817 */ 818 private void updateUi() { 819 final boolean canInput = mSaveAndFinishWorker == null; 820 String password = mPasswordEntry.getText().toString(); 821 final int length = password.length(); 822 if (mUiStage == Stage.Introduction) { 823 mPasswordRestrictionView.setVisibility(View.VISIBLE); 824 final int errorCode = validatePassword(password); 825 String[] messages = convertErrorCodeToMessages(errorCode); 826 // Update the fulfillment of requirements. 827 mPasswordRequirementAdapter.setRequirements(messages); 828 // Enable/Disable the next button accordingly. 829 setNextEnabled(errorCode == NO_ERROR); 830 } else { 831 // Hide password requirement view when we are just asking user to confirm the pw. 832 mPasswordRestrictionView.setVisibility(View.GONE); 833 setHeaderText(getString(mUiStage.getHint(mIsAlphaMode, mForFingerprint))); 834 setNextEnabled(canInput && length > 0); 835 } 836 setNextText(mUiStage.buttonText); 837 mPasswordEntryInputDisabler.setInputEnabled(canInput); 838 } 839 840 private void setHeaderText(String text) { 841 // Only set the text if it is different than the existing one to avoid announcing again. 842 if (!TextUtils.isEmpty(mLayout.getHeaderText()) 843 && mLayout.getHeaderText().toString().equals(text)) { 844 return; 845 } 846 mLayout.setHeaderText(text); 847 } 848 849 public void afterTextChanged(Editable s) { 850 // Changing the text while error displayed resets to NeedToConfirm state 851 if (mUiStage == Stage.ConfirmWrong) { 852 mUiStage = Stage.NeedToConfirm; 853 } 854 // Schedule the UI update. 855 mTextChangedHandler.notifyAfterTextChanged(); 856 } 857 858 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 859 860 } 861 862 public void onTextChanged(CharSequence s, int start, int before, int count) { 863 864 } 865 866 private void startSaveAndFinish() { 867 if (mSaveAndFinishWorker != null) { 868 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker."); 869 return; 870 } 871 872 mPasswordEntryInputDisabler.setInputEnabled(false); 873 setNextEnabled(false); 874 875 mSaveAndFinishWorker = new SaveAndFinishWorker(); 876 mSaveAndFinishWorker.setListener(this); 877 878 getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, 879 FRAGMENT_TAG_SAVE_AND_FINISH).commit(); 880 getFragmentManager().executePendingTransactions(); 881 882 final boolean required = getActivity().getIntent().getBooleanExtra( 883 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 884 mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge, 885 mChosenPassword, mCurrentPassword, mRequestedQuality, mUserId); 886 } 887 888 @Override 889 public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { 890 getActivity().setResult(RESULT_FINISHED, resultData); 891 892 if (!wasSecureBefore) { 893 Intent intent = getRedactionInterstitialIntent(getActivity()); 894 if (intent != null) { 895 intent.putExtra(EXTRA_HIDE_DRAWER, mHideDrawer); 896 startActivity(intent); 897 } 898 } 899 getActivity().finish(); 900 } 901 902 class TextChangedHandler extends Handler { 903 private static final int ON_TEXT_CHANGED = 1; 904 private static final int DELAY_IN_MILLISECOND = 100; 905 906 /** 907 * With the introduction of delay, we batch processing the text changed event to reduce 908 * unnecessary UI updates. 909 */ 910 private void notifyAfterTextChanged() { 911 removeMessages(ON_TEXT_CHANGED); 912 sendEmptyMessageDelayed(ON_TEXT_CHANGED, DELAY_IN_MILLISECOND); 913 } 914 915 @Override 916 public void handleMessage(Message msg) { 917 if (getActivity() == null) { 918 return; 919 } 920 if (msg.what == ON_TEXT_CHANGED) { 921 updateUi(); 922 } 923 } 924 } 925 } 926 927 public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase { 928 929 private String mChosenPassword; 930 private String mCurrentPassword; 931 private int mRequestedQuality; 932 933 public void start(LockPatternUtils utils, boolean required, 934 boolean hasChallenge, long challenge, 935 String chosenPassword, String currentPassword, int requestedQuality, int userId) { 936 prepare(utils, required, hasChallenge, challenge, userId); 937 938 mChosenPassword = chosenPassword; 939 mCurrentPassword = currentPassword; 940 mRequestedQuality = requestedQuality; 941 mUserId = userId; 942 943 start(); 944 } 945 946 @Override 947 protected Intent saveAndVerifyInBackground() { 948 Intent result = null; 949 mUtils.saveLockPassword(mChosenPassword, mCurrentPassword, mRequestedQuality, 950 mUserId); 951 952 if (mHasChallenge) { 953 byte[] token; 954 try { 955 token = mUtils.verifyPassword(mChosenPassword, mChallenge, mUserId); 956 } catch (RequestThrottledException e) { 957 token = null; 958 } 959 960 if (token == null) { 961 Log.e(TAG, "critical: no token returned for known good password."); 962 } 963 964 result = new Intent(); 965 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); 966 } 967 968 return result; 969 } 970 } 971} 972