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.password; 18 19 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; 20 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; 21 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; 22 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; 23 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 24 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; 25 26 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY; 27 28 import android.app.Activity; 29 import android.app.admin.DevicePolicyManager; 30 import android.app.admin.DevicePolicyManager.PasswordComplexity; 31 import android.app.admin.PasswordMetrics; 32 import android.app.settings.SettingsEnums; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.res.Resources.Theme; 36 import android.graphics.Insets; 37 import android.graphics.Typeface; 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.os.Message; 41 import android.text.Editable; 42 import android.text.InputType; 43 import android.text.Selection; 44 import android.text.Spannable; 45 import android.text.TextUtils; 46 import android.text.TextWatcher; 47 import android.util.Log; 48 import android.util.Pair; 49 import android.view.KeyEvent; 50 import android.view.LayoutInflater; 51 import android.view.View; 52 import android.view.ViewGroup; 53 import android.view.inputmethod.EditorInfo; 54 import android.widget.LinearLayout; 55 import android.widget.TextView; 56 import android.widget.TextView.OnEditorActionListener; 57 58 import androidx.annotation.StringRes; 59 import androidx.fragment.app.Fragment; 60 import androidx.recyclerview.widget.LinearLayoutManager; 61 import androidx.recyclerview.widget.RecyclerView; 62 63 import com.android.internal.annotations.VisibleForTesting; 64 import com.android.internal.widget.LockPatternUtils; 65 import com.android.internal.widget.LockPatternUtils.RequestThrottledException; 66 import com.android.internal.widget.TextViewInputDisabler; 67 import com.android.settings.EncryptionInterstitial; 68 import com.android.settings.R; 69 import com.android.settings.SettingsActivity; 70 import com.android.settings.SetupWizardUtils; 71 import com.android.settings.Utils; 72 import com.android.settings.core.InstrumentedFragment; 73 import com.android.settings.notification.RedactionInterstitial; 74 import com.android.settings.widget.ImeAwareEditText; 75 76 import com.google.android.setupcompat.template.FooterBarMixin; 77 import com.google.android.setupcompat.template.FooterButton; 78 import com.google.android.setupdesign.GlifLayout; 79 80 import java.util.ArrayList; 81 import java.util.Arrays; 82 import java.util.List; 83 84 public class ChooseLockPassword extends SettingsActivity { 85 private static final String TAG = "ChooseLockPassword"; 86 87 @Override getIntent()88 public Intent getIntent() { 89 Intent modIntent = new Intent(super.getIntent()); 90 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName()); 91 return modIntent; 92 } 93 94 @Override onApplyThemeResource(Theme theme, int resid, boolean first)95 protected void onApplyThemeResource(Theme theme, int resid, boolean first) { 96 resid = SetupWizardUtils.getTheme(getIntent()); 97 super.onApplyThemeResource(theme, resid, first); 98 } 99 100 public static class IntentBuilder { 101 102 private final Intent mIntent; 103 IntentBuilder(Context context)104 public IntentBuilder(Context context) { 105 mIntent = new Intent(context, ChooseLockPassword.class); 106 mIntent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, false); 107 mIntent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, false); 108 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 109 } 110 setPasswordQuality(int quality)111 public IntentBuilder setPasswordQuality(int quality) { 112 mIntent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality); 113 return this; 114 } 115 setUserId(int userId)116 public IntentBuilder setUserId(int userId) { 117 mIntent.putExtra(Intent.EXTRA_USER_ID, userId); 118 return this; 119 } 120 setChallenge(long challenge)121 public IntentBuilder setChallenge(long challenge) { 122 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true); 123 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge); 124 return this; 125 } 126 setPassword(byte[] password)127 public IntentBuilder setPassword(byte[] password) { 128 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, password); 129 return this; 130 } 131 setForFingerprint(boolean forFingerprint)132 public IntentBuilder setForFingerprint(boolean forFingerprint) { 133 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, forFingerprint); 134 return this; 135 } 136 setForFace(boolean forFace)137 public IntentBuilder setForFace(boolean forFace) { 138 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, forFace); 139 return this; 140 } 141 setRequestedMinComplexity(@asswordComplexity int level)142 public IntentBuilder setRequestedMinComplexity(@PasswordComplexity int level) { 143 mIntent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, level); 144 return this; 145 } 146 build()147 public Intent build() { 148 return mIntent; 149 } 150 } 151 152 @Override isValidFragment(String fragmentName)153 protected boolean isValidFragment(String fragmentName) { 154 if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true; 155 return false; 156 } 157 getFragmentClass()158 /* package */ Class<? extends Fragment> getFragmentClass() { 159 return ChooseLockPasswordFragment.class; 160 } 161 162 @Override onCreate(Bundle savedInstanceState)163 protected void onCreate(Bundle savedInstanceState) { 164 super.onCreate(savedInstanceState); 165 final boolean forFingerprint = getIntent() 166 .getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false); 167 final boolean forFace = getIntent() 168 .getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false); 169 170 CharSequence msg = getText(R.string.lockpassword_choose_your_screen_lock_header); 171 if (forFingerprint) { 172 msg = getText(R.string.lockpassword_choose_your_password_header_for_fingerprint); 173 } else if (forFace) { 174 msg = getText(R.string.lockpassword_choose_your_password_header_for_face); 175 } 176 177 setTitle(msg); 178 findViewById(R.id.content_parent).setFitsSystemWindows(false); 179 } 180 181 public static class ChooseLockPasswordFragment extends InstrumentedFragment 182 implements OnEditorActionListener, TextWatcher, SaveAndFinishWorker.Listener { 183 private static final String KEY_FIRST_PIN = "first_pin"; 184 private static final String KEY_UI_STAGE = "ui_stage"; 185 private static final String KEY_CURRENT_PASSWORD = "current_password"; 186 private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker"; 187 188 private byte[] mCurrentPassword; 189 private byte[] mChosenPassword; 190 private boolean mHasChallenge; 191 private long mChallenge; 192 private ImeAwareEditText mPasswordEntry; 193 private TextViewInputDisabler mPasswordEntryInputDisabler; 194 private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE; 195 private int mPasswordMaxLength = 16; 196 private int mPasswordMinLetters = 0; 197 private int mPasswordMinUpperCase = 0; 198 private int mPasswordMinLowerCase = 0; 199 private int mPasswordMinSymbols = 0; 200 private int mPasswordMinNumeric = 0; 201 private int mPasswordMinNonLetter = 0; 202 private int mPasswordMinLengthToFulfillAllPolicies = 0; 203 private boolean mPasswordNumSequenceAllowed = true; 204 @PasswordComplexity private int mRequestedMinComplexity = PASSWORD_COMPLEXITY_NONE; 205 protected int mUserId; 206 private byte[] mPasswordHistoryHashFactor; 207 208 private LockPatternUtils mLockPatternUtils; 209 private SaveAndFinishWorker mSaveAndFinishWorker; 210 private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 211 private ChooseLockSettingsHelper mChooseLockSettingsHelper; 212 protected Stage mUiStage = Stage.Introduction; 213 private PasswordRequirementAdapter mPasswordRequirementAdapter; 214 private GlifLayout mLayout; 215 protected boolean mForFingerprint; 216 protected boolean mForFace; 217 218 private byte[] mFirstPin; 219 private RecyclerView mPasswordRestrictionView; 220 protected boolean mIsAlphaMode; 221 protected FooterButton mSkipOrClearButton; 222 private FooterButton mNextButton; 223 private TextView mMessage; 224 225 private TextChangedHandler mTextChangedHandler; 226 227 private static final int CONFIRM_EXISTING_REQUEST = 58; 228 static final int RESULT_FINISHED = RESULT_FIRST_USER; 229 230 private static final int MIN_LETTER_IN_PASSWORD = 0; 231 private static final int MIN_UPPER_LETTERS_IN_PASSWORD = 1; 232 private static final int MIN_LOWER_LETTERS_IN_PASSWORD = 2; 233 private static final int MIN_SYMBOLS_IN_PASSWORD = 3; 234 private static final int MIN_NUMBER_IN_PASSWORD = 4; 235 private static final int MIN_NON_LETTER_IN_PASSWORD = 5; 236 237 // Error code returned from {@link #validatePassword(byte[])}. 238 static final int NO_ERROR = 0; 239 static final int CONTAIN_INVALID_CHARACTERS = 1 << 0; 240 static final int TOO_SHORT = 1 << 1; 241 static final int TOO_LONG = 1 << 2; 242 static final int CONTAIN_NON_DIGITS = 1 << 3; 243 static final int CONTAIN_SEQUENTIAL_DIGITS = 1 << 4; 244 static final int RECENTLY_USED = 1 << 5; 245 static final int NOT_ENOUGH_LETTER = 1 << 6; 246 static final int NOT_ENOUGH_UPPER_CASE = 1 << 7; 247 static final int NOT_ENOUGH_LOWER_CASE = 1 << 8; 248 static final int NOT_ENOUGH_DIGITS = 1 << 9; 249 static final int NOT_ENOUGH_SYMBOLS = 1 << 10; 250 static final int NOT_ENOUGH_NON_LETTER = 1 << 11; 251 252 /** 253 * Keep track internally of where the user is in choosing a pattern. 254 */ 255 protected enum Stage { 256 257 Introduction( 258 R.string.lockpassword_choose_your_screen_lock_header, // password 259 R.string.lockpassword_choose_your_password_header_for_fingerprint, 260 R.string.lockpassword_choose_your_password_header_for_face, 261 R.string.lockpassword_choose_your_screen_lock_header, // pin 262 R.string.lockpassword_choose_your_pin_header_for_fingerprint, 263 R.string.lockpassword_choose_your_pin_header_for_face, 264 R.string.lockpassword_choose_your_password_message, // added security message 265 R.string.lock_settings_picker_biometrics_added_security_message, 266 R.string.lockpassword_choose_your_pin_message, 267 R.string.lock_settings_picker_biometrics_added_security_message, 268 R.string.next_label), 269 270 NeedToConfirm( 271 R.string.lockpassword_confirm_your_password_header, 272 R.string.lockpassword_confirm_your_password_header, 273 R.string.lockpassword_confirm_your_password_header, 274 R.string.lockpassword_confirm_your_pin_header, 275 R.string.lockpassword_confirm_your_pin_header, 276 R.string.lockpassword_confirm_your_pin_header, 277 0, 278 0, 279 0, 280 0, 281 R.string.lockpassword_confirm_label), 282 283 ConfirmWrong( 284 R.string.lockpassword_confirm_passwords_dont_match, 285 R.string.lockpassword_confirm_passwords_dont_match, 286 R.string.lockpassword_confirm_passwords_dont_match, 287 R.string.lockpassword_confirm_pins_dont_match, 288 R.string.lockpassword_confirm_pins_dont_match, 289 R.string.lockpassword_confirm_pins_dont_match, 290 0, 291 0, 292 0, 293 0, 294 R.string.lockpassword_confirm_label); 295 Stage(int hintInAlpha, int hintInAlphaForFingerprint, int hintInAlphaForFace, int hintInNumeric, int hintInNumericForFingerprint, int hintInNumericForFace, int messageInAlpha, int messageInAlphaForBiometrics, int messageInNumeric, int messageInNumericForBiometrics, int nextButtonText)296 Stage(int hintInAlpha, int hintInAlphaForFingerprint, int hintInAlphaForFace, 297 int hintInNumeric, int hintInNumericForFingerprint, int hintInNumericForFace, 298 int messageInAlpha, int messageInAlphaForBiometrics, 299 int messageInNumeric, int messageInNumericForBiometrics, 300 int nextButtonText) { 301 this.alphaHint = hintInAlpha; 302 this.alphaHintForFingerprint = hintInAlphaForFingerprint; 303 this.alphaHintForFace = hintInAlphaForFace; 304 305 this.numericHint = hintInNumeric; 306 this.numericHintForFingerprint = hintInNumericForFingerprint; 307 this.numericHintForFace = hintInNumericForFace; 308 309 this.alphaMessage = messageInAlpha; 310 this.alphaMessageForBiometrics = messageInAlphaForBiometrics; 311 this.numericMessage = messageInNumeric; 312 this.numericMessageForBiometrics = messageInNumericForBiometrics; 313 this.buttonText = nextButtonText; 314 } 315 316 public static final int TYPE_NONE = 0; 317 public static final int TYPE_FINGERPRINT = 1; 318 public static final int TYPE_FACE = 2; 319 320 // Password 321 public final int alphaHint; 322 public final int alphaHintForFingerprint; 323 public final int alphaHintForFace; 324 325 // PIN 326 public final int numericHint; 327 public final int numericHintForFingerprint; 328 public final int numericHintForFace; 329 330 public final int alphaMessage; 331 public final int alphaMessageForBiometrics; 332 public final int numericMessage; 333 public final int numericMessageForBiometrics; 334 public final int buttonText; 335 getHint(boolean isAlpha, int type)336 public @StringRes int getHint(boolean isAlpha, int type) { 337 if (isAlpha) { 338 if (type == TYPE_FINGERPRINT) { 339 return alphaHintForFingerprint; 340 } else if (type == TYPE_FACE) { 341 return alphaHintForFace; 342 } else { 343 return alphaHint; 344 } 345 } else { 346 if (type == TYPE_FINGERPRINT) { 347 return numericHintForFingerprint; 348 } else if (type == TYPE_FACE) { 349 return numericHintForFace; 350 } else { 351 return numericHint; 352 } 353 } 354 } 355 getMessage(boolean isAlpha, int type)356 public @StringRes int getMessage(boolean isAlpha, int type) { 357 if (isAlpha) { 358 return type != TYPE_NONE ? alphaMessageForBiometrics : alphaMessage; 359 } else { 360 return type != TYPE_NONE ? numericMessageForBiometrics : numericMessage; 361 } 362 } 363 } 364 365 // required constructor for fragments ChooseLockPasswordFragment()366 public ChooseLockPasswordFragment() { 367 368 } 369 370 @Override onCreate(Bundle savedInstanceState)371 public void onCreate(Bundle savedInstanceState) { 372 super.onCreate(savedInstanceState); 373 mLockPatternUtils = new LockPatternUtils(getActivity()); 374 Intent intent = getActivity().getIntent(); 375 if (!(getActivity() instanceof ChooseLockPassword)) { 376 throw new SecurityException("Fragment contained in wrong activity"); 377 } 378 // Only take this argument into account if it belongs to the current profile. 379 mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras()); 380 mForFingerprint = intent.getBooleanExtra( 381 ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false); 382 mForFace = intent.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false); 383 mRequestedMinComplexity = intent.getIntExtra( 384 EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE); 385 mRequestedQuality = Math.max( 386 intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, mRequestedQuality), 387 mLockPatternUtils.getRequestedPasswordQuality(mUserId)); 388 389 loadDpmPasswordRequirements(); 390 mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity()); 391 392 if (intent.getBooleanExtra( 393 ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) { 394 SaveAndFinishWorker w = new SaveAndFinishWorker(); 395 final boolean required = getActivity().getIntent().getBooleanExtra( 396 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 397 byte[] currentBytes = intent.getByteArrayExtra( 398 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 399 400 w.setBlocking(true); 401 w.setListener(this); 402 w.start(mChooseLockSettingsHelper.utils(), required, false, 0, 403 currentBytes, currentBytes, mRequestedQuality, mUserId); 404 } 405 mTextChangedHandler = new TextChangedHandler(); 406 } 407 408 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)409 public View onCreateView(LayoutInflater inflater, ViewGroup container, 410 Bundle savedInstanceState) { 411 return inflater.inflate(R.layout.choose_lock_password, container, false); 412 } 413 414 @Override onViewCreated(View view, Bundle savedInstanceState)415 public void onViewCreated(View view, Bundle savedInstanceState) { 416 super.onViewCreated(view, savedInstanceState); 417 418 mLayout = (GlifLayout) view; 419 420 // Make the password container consume the optical insets so the edit text is aligned 421 // with the sides of the parent visually. 422 ViewGroup container = view.findViewById(R.id.password_container); 423 container.setOpticalInsets(Insets.NONE); 424 425 final FooterBarMixin mixin = mLayout.getMixin(FooterBarMixin.class); 426 mixin.setSecondaryButton( 427 new FooterButton.Builder(getActivity()) 428 .setText(R.string.lockpassword_clear_label) 429 .setListener(this::onSkipOrClearButtonClick) 430 .setButtonType(FooterButton.ButtonType.SKIP) 431 .setTheme(R.style.SudGlifButton_Secondary) 432 .build() 433 ); 434 mixin.setPrimaryButton( 435 new FooterButton.Builder(getActivity()) 436 .setText(R.string.next_label) 437 .setListener(this::onNextButtonClick) 438 .setButtonType(FooterButton.ButtonType.NEXT) 439 .setTheme(R.style.SudGlifButton_Primary) 440 .build() 441 ); 442 mSkipOrClearButton = mixin.getSecondaryButton(); 443 mNextButton = mixin.getPrimaryButton(); 444 445 mMessage = view.findViewById(R.id.sud_layout_description); 446 if (mForFingerprint) { 447 mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_fingerprint_header)); 448 } else if (mForFace) { 449 mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_face_header)); 450 } 451 452 mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality 453 || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality 454 || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality; 455 456 setupPasswordRequirementsView(view); 457 458 mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); 459 mPasswordEntry = view.findViewById(R.id.password_entry); 460 mPasswordEntry.setOnEditorActionListener(this); 461 mPasswordEntry.addTextChangedListener(this); 462 mPasswordEntry.requestFocus(); 463 mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry); 464 465 final Activity activity = getActivity(); 466 467 int currentType = mPasswordEntry.getInputType(); 468 mPasswordEntry.setInputType(mIsAlphaMode ? currentType 469 : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)); 470 // Can't set via XML since setInputType resets the fontFamily to null 471 mPasswordEntry.setTypeface(Typeface.create( 472 getContext().getString(com.android.internal.R.string.config_headlineFontFamily), 473 Typeface.NORMAL)); 474 475 Intent intent = getActivity().getIntent(); 476 final boolean confirmCredentials = intent.getBooleanExtra( 477 ChooseLockGeneric.CONFIRM_CREDENTIALS, true); 478 mCurrentPassword = intent.getByteArrayExtra( 479 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 480 mHasChallenge = intent.getBooleanExtra( 481 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 482 mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0); 483 if (savedInstanceState == null) { 484 updateStage(Stage.Introduction); 485 if (confirmCredentials) { 486 mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, 487 getString(R.string.unlock_set_unlock_launch_picker_title), true, 488 mUserId); 489 } 490 } else { 491 492 // restore from previous state 493 mFirstPin = savedInstanceState.getByteArray(KEY_FIRST_PIN); 494 final String state = savedInstanceState.getString(KEY_UI_STAGE); 495 if (state != null) { 496 mUiStage = Stage.valueOf(state); 497 updateStage(mUiStage); 498 } 499 500 if (mCurrentPassword == null) { 501 mCurrentPassword = savedInstanceState.getByteArray(KEY_CURRENT_PASSWORD); 502 } 503 504 // Re-attach to the exiting worker if there is one. 505 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag( 506 FRAGMENT_TAG_SAVE_AND_FINISH); 507 } 508 509 if (activity instanceof SettingsActivity) { 510 final SettingsActivity sa = (SettingsActivity) activity; 511 int title = Stage.Introduction.getHint(mIsAlphaMode, getStageType()); 512 sa.setTitle(title); 513 mLayout.setHeaderText(title); 514 } 515 } 516 getStageType()517 protected int getStageType() { 518 return mForFingerprint ? Stage.TYPE_FINGERPRINT : 519 mForFace ? Stage.TYPE_FACE : 520 Stage.TYPE_NONE; 521 } 522 setupPasswordRequirementsView(View view)523 private void setupPasswordRequirementsView(View view) { 524 mPasswordRestrictionView = view.findViewById(R.id.password_requirements_view); 525 mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); 526 mPasswordRequirementAdapter = new PasswordRequirementAdapter(); 527 mPasswordRestrictionView.setAdapter(mPasswordRequirementAdapter); 528 } 529 530 @Override getMetricsCategory()531 public int getMetricsCategory() { 532 return SettingsEnums.CHOOSE_LOCK_PASSWORD; 533 } 534 535 @Override onResume()536 public void onResume() { 537 super.onResume(); 538 updateStage(mUiStage); 539 if (mSaveAndFinishWorker != null) { 540 mSaveAndFinishWorker.setListener(this); 541 } else { 542 mPasswordEntry.requestFocus(); 543 mPasswordEntry.scheduleShowSoftInput(); 544 } 545 } 546 547 @Override onPause()548 public void onPause() { 549 if (mSaveAndFinishWorker != null) { 550 mSaveAndFinishWorker.setListener(null); 551 } 552 super.onPause(); 553 } 554 555 @Override onSaveInstanceState(Bundle outState)556 public void onSaveInstanceState(Bundle outState) { 557 super.onSaveInstanceState(outState); 558 outState.putString(KEY_UI_STAGE, mUiStage.name()); 559 outState.putByteArray(KEY_FIRST_PIN, mFirstPin); 560 outState.putByteArray(KEY_CURRENT_PASSWORD, mCurrentPassword); 561 } 562 563 @Override onActivityResult(int requestCode, int resultCode, Intent data)564 public void onActivityResult(int requestCode, int resultCode, 565 Intent data) { 566 super.onActivityResult(requestCode, resultCode, data); 567 switch (requestCode) { 568 case CONFIRM_EXISTING_REQUEST: 569 if (resultCode != Activity.RESULT_OK) { 570 getActivity().setResult(RESULT_FINISHED); 571 getActivity().finish(); 572 } else { 573 mCurrentPassword = data.getByteArrayExtra( 574 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 575 } 576 break; 577 } 578 } 579 getRedactionInterstitialIntent(Context context)580 protected Intent getRedactionInterstitialIntent(Context context) { 581 return RedactionInterstitial.createStartIntent(context, mUserId); 582 } 583 updateStage(Stage stage)584 protected void updateStage(Stage stage) { 585 final Stage previousStage = mUiStage; 586 mUiStage = stage; 587 updateUi(); 588 589 // If the stage changed, announce the header for accessibility. This 590 // is a no-op when accessibility is disabled. 591 if (previousStage != stage) { 592 mLayout.announceForAccessibility(mLayout.getHeaderText()); 593 } 594 } 595 596 /** 597 * Read the requirements from {@link DevicePolicyManager} and intent and aggregate them. 598 */ loadDpmPasswordRequirements()599 private void loadDpmPasswordRequirements() { 600 final int dpmPasswordQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId); 601 if (dpmPasswordQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) { 602 mPasswordNumSequenceAllowed = false; 603 } 604 mPasswordMinLength = Math.max(LockPatternUtils.MIN_LOCK_PASSWORD_SIZE, 605 mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId)); 606 mPasswordMaxLength = mLockPatternUtils.getMaximumPasswordLength(mRequestedQuality); 607 mPasswordMinLetters = mLockPatternUtils.getRequestedPasswordMinimumLetters(mUserId); 608 mPasswordMinUpperCase = mLockPatternUtils.getRequestedPasswordMinimumUpperCase(mUserId); 609 mPasswordMinLowerCase = mLockPatternUtils.getRequestedPasswordMinimumLowerCase(mUserId); 610 mPasswordMinNumeric = mLockPatternUtils.getRequestedPasswordMinimumNumeric(mUserId); 611 mPasswordMinSymbols = mLockPatternUtils.getRequestedPasswordMinimumSymbols(mUserId); 612 mPasswordMinNonLetter = mLockPatternUtils.getRequestedPasswordMinimumNonLetter(mUserId); 613 614 // Modify the value based on dpm policy 615 switch (dpmPasswordQuality) { 616 case PASSWORD_QUALITY_ALPHABETIC: 617 if (mPasswordMinLetters == 0) { 618 mPasswordMinLetters = 1; 619 } 620 break; 621 case PASSWORD_QUALITY_ALPHANUMERIC: 622 if (mPasswordMinLetters == 0) { 623 mPasswordMinLetters = 1; 624 } 625 if (mPasswordMinNumeric == 0) { 626 mPasswordMinNumeric = 1; 627 } 628 break; 629 case PASSWORD_QUALITY_COMPLEX: 630 // Reserve all the requirements. 631 break; 632 default: 633 mPasswordMinNumeric = 0; 634 mPasswordMinLetters = 0; 635 mPasswordMinUpperCase = 0; 636 mPasswordMinLowerCase = 0; 637 mPasswordMinSymbols = 0; 638 mPasswordMinNonLetter = 0; 639 } 640 641 mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies(); 642 } 643 644 /** 645 * Merges the dpm requirements and the min complexity requirements. 646 * 647 * <p>Since there are more than one set of metrics to meet the min complexity requirement, 648 * and we are not hard-coding any one of them to be the requirements the user must fulfil, 649 * we are taking what the user has already entered into account when compiling the list of 650 * requirements from min complexity. Then we merge this list with the DPM requirements, and 651 * present the merged set as validation results to the user on the UI. 652 * 653 * <p>For example, suppose min complexity requires either ALPHABETIC(8+), or 654 * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI 655 * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering 656 * an alphanumeric password so we would update the min complexity required min length to 6. 657 * This might result in a little confusion for the user but the UI does not support showing 658 * multiple sets of requirements / validation results as options to users, this is the best 659 * we can do now. 660 */ mergeMinComplexityAndDpmRequirements(int userEnteredPasswordQuality)661 private void mergeMinComplexityAndDpmRequirements(int userEnteredPasswordQuality) { 662 if (mRequestedMinComplexity == PASSWORD_COMPLEXITY_NONE) { 663 // dpm requirements are dominant if min complexity is none 664 return; 665 } 666 667 // reset dpm requirements 668 loadDpmPasswordRequirements(); 669 670 PasswordMetrics minMetrics = PasswordMetrics.getMinimumMetrics( 671 mRequestedMinComplexity, userEnteredPasswordQuality, mRequestedQuality, 672 requiresNumeric(), requiresLettersOrSymbols()); 673 mPasswordNumSequenceAllowed = mPasswordNumSequenceAllowed 674 && minMetrics.quality != PASSWORD_QUALITY_NUMERIC_COMPLEX; 675 mPasswordMinLength = Math.max(mPasswordMinLength, minMetrics.length); 676 mPasswordMinLetters = Math.max(mPasswordMinLetters, minMetrics.letters); 677 mPasswordMinUpperCase = Math.max(mPasswordMinUpperCase, minMetrics.upperCase); 678 mPasswordMinLowerCase = Math.max(mPasswordMinLowerCase, minMetrics.lowerCase); 679 mPasswordMinNumeric = Math.max(mPasswordMinNumeric, minMetrics.numeric); 680 mPasswordMinSymbols = Math.max(mPasswordMinSymbols, minMetrics.symbols); 681 mPasswordMinNonLetter = Math.max(mPasswordMinNonLetter, minMetrics.nonLetter); 682 683 if (minMetrics.quality == PASSWORD_QUALITY_ALPHABETIC) { 684 if (!requiresLettersOrSymbols()) { 685 mPasswordMinLetters = 1; 686 } 687 } 688 if (minMetrics.quality == PASSWORD_QUALITY_ALPHANUMERIC) { 689 if (!requiresLettersOrSymbols()) { 690 mPasswordMinLetters = 1; 691 } 692 if (!requiresNumeric()) { 693 mPasswordMinNumeric = 1; 694 } 695 } 696 697 mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies(); 698 } 699 requiresLettersOrSymbols()700 private boolean requiresLettersOrSymbols() { 701 // This is the condition for the password to be considered ALPHABETIC according to 702 // PasswordMetrics.computeForPassword() 703 return mPasswordMinLetters + mPasswordMinUpperCase 704 + mPasswordMinLowerCase + mPasswordMinSymbols + mPasswordMinNonLetter > 0; 705 } 706 requiresNumeric()707 private boolean requiresNumeric() { 708 return mPasswordMinNumeric > 0; 709 } 710 711 /** 712 * Validates PIN/Password and returns the validation result. 713 * 714 * @param password the raw password the user typed in 715 * @return the validation result. 716 */ 717 @VisibleForTesting validatePassword(byte[] password)718 int validatePassword(byte[] password) { 719 int errorCode = NO_ERROR; 720 final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password); 721 mergeMinComplexityAndDpmRequirements(metrics.quality); 722 723 if (password == null || password.length < mPasswordMinLength) { 724 if (mPasswordMinLength > mPasswordMinLengthToFulfillAllPolicies) { 725 errorCode |= TOO_SHORT; 726 } 727 } else if (password.length > mPasswordMaxLength) { 728 errorCode |= TOO_LONG; 729 } else { 730 // The length requirements are fulfilled. 731 if (!mPasswordNumSequenceAllowed 732 && !requiresLettersOrSymbols() 733 && metrics.numeric == password.length) { 734 // Check for repeated characters or sequences (e.g. '1234', '0000', '2468') 735 // if DevicePolicyManager or min password complexity requires a complex numeric 736 // password. There can be two cases in the UI: 1. User chooses to enroll a 737 // PIN, 2. User chooses to enroll a password but enters a numeric-only pin. We 738 // should carry out the sequence check in both cases. 739 // 740 // Conditions for the !requiresLettersOrSymbols() to be necessary: 741 // - DPM requires NUMERIC_COMPLEX 742 // - min complexity not NONE, user picks PASSWORD type so ALPHABETIC or 743 // ALPHANUMERIC is required 744 // Imagine user has entered "12345678", if we don't skip the sequence check, the 745 // validation result would show both "requires a letter" and "sequence not 746 // allowed", while the only requirement the user needs to know is "requires a 747 // letter" because once the user has fulfilled the alphabetic requirement, the 748 // password would not be containing only digits so this check would not be 749 // performed anyway. 750 final int sequence = PasswordMetrics.maxLengthSequence(password); 751 if (sequence > PasswordMetrics.MAX_ALLOWED_SEQUENCE) { 752 errorCode |= CONTAIN_SEQUENTIAL_DIGITS; 753 } 754 } 755 // Is the password recently used? 756 if (mLockPatternUtils.checkPasswordHistory(password, getPasswordHistoryHashFactor(), 757 mUserId)) { 758 errorCode |= RECENTLY_USED; 759 } 760 } 761 762 // Allow non-control Latin-1 characters only. 763 for (int i = 0; i < password.length; i++) { 764 char c = (char) password[i]; 765 if (c < 32 || c > 127) { 766 errorCode |= CONTAIN_INVALID_CHARACTERS; 767 break; 768 } 769 } 770 771 // Ensure no non-digits if we are requesting numbers. This shouldn't be possible unless 772 // user finds some way to bring up soft keyboard. 773 if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC 774 || mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) { 775 if (metrics.letters > 0 || metrics.symbols > 0) { 776 errorCode |= CONTAIN_NON_DIGITS; 777 } 778 } 779 780 if (metrics.letters < mPasswordMinLetters) { 781 errorCode |= NOT_ENOUGH_LETTER; 782 } 783 if (metrics.upperCase < mPasswordMinUpperCase) { 784 errorCode |= NOT_ENOUGH_UPPER_CASE; 785 } 786 if (metrics.lowerCase < mPasswordMinLowerCase) { 787 errorCode |= NOT_ENOUGH_LOWER_CASE; 788 } 789 if (metrics.symbols < mPasswordMinSymbols) { 790 errorCode |= NOT_ENOUGH_SYMBOLS; 791 } 792 if (metrics.numeric < mPasswordMinNumeric) { 793 errorCode |= NOT_ENOUGH_DIGITS; 794 } 795 if (metrics.nonLetter < mPasswordMinNonLetter) { 796 errorCode |= NOT_ENOUGH_NON_LETTER; 797 } 798 return errorCode; 799 } 800 801 /** 802 * Lazily compute and return the history hash factor of the current user (mUserId), used for 803 * password history check. 804 */ getPasswordHistoryHashFactor()805 private byte[] getPasswordHistoryHashFactor() { 806 if (mPasswordHistoryHashFactor == null) { 807 mPasswordHistoryHashFactor = mLockPatternUtils.getPasswordHistoryHashFactor( 808 mCurrentPassword, mUserId); 809 } 810 return mPasswordHistoryHashFactor; 811 } 812 handleNext()813 public void handleNext() { 814 if (mSaveAndFinishWorker != null) return; 815 // TODO(b/120484642): This is a point of entry for passwords from the UI 816 mChosenPassword = LockPatternUtils.charSequenceToByteArray(mPasswordEntry.getText()); 817 if (mChosenPassword == null || mChosenPassword.length == 0) { 818 return; 819 } 820 if (mUiStage == Stage.Introduction) { 821 if (validatePassword(mChosenPassword) == NO_ERROR) { 822 mFirstPin = mChosenPassword; 823 mPasswordEntry.setText(""); 824 updateStage(Stage.NeedToConfirm); 825 } else { 826 Arrays.fill(mChosenPassword, (byte) 0); 827 } 828 } else if (mUiStage == Stage.NeedToConfirm) { 829 if (Arrays.equals(mFirstPin, mChosenPassword)) { 830 startSaveAndFinish(); 831 } else { 832 CharSequence tmp = mPasswordEntry.getText(); 833 if (tmp != null) { 834 Selection.setSelection((Spannable) tmp, 0, tmp.length()); 835 } 836 updateStage(Stage.ConfirmWrong); 837 Arrays.fill(mChosenPassword, (byte) 0); 838 } 839 } 840 } 841 setNextEnabled(boolean enabled)842 protected void setNextEnabled(boolean enabled) { 843 mNextButton.setEnabled(enabled); 844 } 845 setNextText(int text)846 protected void setNextText(int text) { 847 mNextButton.setText(getActivity(), text); 848 } 849 onSkipOrClearButtonClick(View view)850 protected void onSkipOrClearButtonClick(View view) { 851 mPasswordEntry.setText(""); 852 } 853 onNextButtonClick(View view)854 protected void onNextButtonClick(View view) { 855 handleNext(); 856 } 857 onEditorAction(TextView v, int actionId, KeyEvent event)858 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 859 // Check if this was the result of hitting the enter or "done" key 860 if (actionId == EditorInfo.IME_NULL 861 || actionId == EditorInfo.IME_ACTION_DONE 862 || actionId == EditorInfo.IME_ACTION_NEXT) { 863 handleNext(); 864 return true; 865 } 866 return false; 867 } 868 869 /** 870 * @param errorCode error code returned from {@link #validatePassword(String)}. 871 * @return an array of messages describing the error, important messages come first. 872 */ convertErrorCodeToMessages(int errorCode)873 String[] convertErrorCodeToMessages(int errorCode) { 874 List<String> messages = new ArrayList<>(); 875 if ((errorCode & CONTAIN_INVALID_CHARACTERS) > 0) { 876 messages.add(getString(R.string.lockpassword_illegal_character)); 877 } 878 if ((errorCode & CONTAIN_NON_DIGITS) > 0) { 879 messages.add(getString(R.string.lockpassword_pin_contains_non_digits)); 880 } 881 if ((errorCode & NOT_ENOUGH_UPPER_CASE) > 0) { 882 messages.add(getResources().getQuantityString( 883 R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase, 884 mPasswordMinUpperCase)); 885 } 886 if ((errorCode & NOT_ENOUGH_LOWER_CASE) > 0) { 887 messages.add(getResources().getQuantityString( 888 R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase, 889 mPasswordMinLowerCase)); 890 } 891 if ((errorCode & NOT_ENOUGH_LETTER) > 0) { 892 messages.add(getResources().getQuantityString( 893 R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters, 894 mPasswordMinLetters)); 895 } 896 if ((errorCode & NOT_ENOUGH_DIGITS) > 0) { 897 messages.add(getResources().getQuantityString( 898 R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric, 899 mPasswordMinNumeric)); 900 } 901 if ((errorCode & NOT_ENOUGH_SYMBOLS) > 0) { 902 messages.add(getResources().getQuantityString( 903 R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols, 904 mPasswordMinSymbols)); 905 } 906 if ((errorCode & NOT_ENOUGH_NON_LETTER) > 0) { 907 messages.add(getResources().getQuantityString( 908 R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter, 909 mPasswordMinNonLetter)); 910 } 911 if ((errorCode & TOO_SHORT) > 0) { 912 messages.add(getResources().getQuantityString( 913 mIsAlphaMode 914 ? R.plurals.lockpassword_password_too_short 915 : R.plurals.lockpassword_pin_too_short, 916 mPasswordMinLength, 917 mPasswordMinLength)); 918 } 919 if ((errorCode & TOO_LONG) > 0) { 920 messages.add(getResources().getQuantityString( 921 mIsAlphaMode 922 ? R.plurals.lockpassword_password_too_long 923 : R.plurals.lockpassword_pin_too_long, 924 mPasswordMaxLength + 1, 925 mPasswordMaxLength + 1)); 926 } 927 if ((errorCode & CONTAIN_SEQUENTIAL_DIGITS) > 0) { 928 messages.add(getString(R.string.lockpassword_pin_no_sequential_digits)); 929 } 930 if ((errorCode & RECENTLY_USED) > 0) { 931 messages.add(getString((mIsAlphaMode) ? R.string.lockpassword_password_recently_used 932 : R.string.lockpassword_pin_recently_used)); 933 } 934 return messages.toArray(new String[0]); 935 } 936 getMinLengthToFulfillAllPolicies()937 private int getMinLengthToFulfillAllPolicies() { 938 final int minLengthForLetters = Math.max(mPasswordMinLetters, 939 mPasswordMinUpperCase + mPasswordMinLowerCase); 940 final int minLengthForNonLetters = Math.max(mPasswordMinNonLetter, 941 mPasswordMinSymbols + mPasswordMinNumeric); 942 return minLengthForLetters + minLengthForNonLetters; 943 } 944 945 /** 946 * Update the hint based on current Stage and length of password entry 947 */ updateUi()948 protected void updateUi() { 949 final boolean canInput = mSaveAndFinishWorker == null; 950 byte[] password = LockPatternUtils.charSequenceToByteArray(mPasswordEntry.getText()); 951 final int length = password.length; 952 if (mUiStage == Stage.Introduction) { 953 mPasswordRestrictionView.setVisibility(View.VISIBLE); 954 final int errorCode = validatePassword(password); 955 String[] messages = convertErrorCodeToMessages(errorCode); 956 // Update the fulfillment of requirements. 957 mPasswordRequirementAdapter.setRequirements(messages); 958 // Enable/Disable the next button accordingly. 959 setNextEnabled(errorCode == NO_ERROR); 960 } else { 961 // Hide password requirement view when we are just asking user to confirm the pw. 962 mPasswordRestrictionView.setVisibility(View.GONE); 963 setHeaderText(getString(mUiStage.getHint(mIsAlphaMode, getStageType()))); 964 setNextEnabled(canInput && length >= mPasswordMinLength); 965 mSkipOrClearButton.setVisibility(toVisibility(canInput && length > 0)); 966 } 967 int message = mUiStage.getMessage(mIsAlphaMode, getStageType()); 968 if (message != 0) { 969 mMessage.setVisibility(View.VISIBLE); 970 mMessage.setText(message); 971 } else { 972 mMessage.setVisibility(View.INVISIBLE); 973 } 974 975 setNextText(mUiStage.buttonText); 976 mPasswordEntryInputDisabler.setInputEnabled(canInput); 977 Arrays.fill(password, (byte) 0); 978 } 979 toVisibility(boolean visibleOrGone)980 protected int toVisibility(boolean visibleOrGone) { 981 return visibleOrGone ? View.VISIBLE : View.GONE; 982 } 983 setHeaderText(String text)984 private void setHeaderText(String text) { 985 // Only set the text if it is different than the existing one to avoid announcing again. 986 if (!TextUtils.isEmpty(mLayout.getHeaderText()) 987 && mLayout.getHeaderText().toString().equals(text)) { 988 return; 989 } 990 mLayout.setHeaderText(text); 991 } 992 afterTextChanged(Editable s)993 public void afterTextChanged(Editable s) { 994 // Changing the text while error displayed resets to NeedToConfirm state 995 if (mUiStage == Stage.ConfirmWrong) { 996 mUiStage = Stage.NeedToConfirm; 997 } 998 // Schedule the UI update. 999 mTextChangedHandler.notifyAfterTextChanged(); 1000 } 1001 beforeTextChanged(CharSequence s, int start, int count, int after)1002 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 1003 1004 } 1005 onTextChanged(CharSequence s, int start, int before, int count)1006 public void onTextChanged(CharSequence s, int start, int before, int count) { 1007 1008 } 1009 startSaveAndFinish()1010 private void startSaveAndFinish() { 1011 if (mSaveAndFinishWorker != null) { 1012 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker."); 1013 return; 1014 } 1015 1016 mPasswordEntryInputDisabler.setInputEnabled(false); 1017 setNextEnabled(false); 1018 1019 mSaveAndFinishWorker = new SaveAndFinishWorker(); 1020 mSaveAndFinishWorker.setListener(this); 1021 1022 getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, 1023 FRAGMENT_TAG_SAVE_AND_FINISH).commit(); 1024 getFragmentManager().executePendingTransactions(); 1025 1026 final boolean required = getActivity().getIntent().getBooleanExtra( 1027 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 1028 mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge, 1029 mChosenPassword, mCurrentPassword, mRequestedQuality, mUserId); 1030 } 1031 1032 @Override onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)1033 public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { 1034 getActivity().setResult(RESULT_FINISHED, resultData); 1035 1036 if (mChosenPassword != null) { 1037 Arrays.fill(mChosenPassword, (byte) 0); 1038 } 1039 if (mCurrentPassword != null) { 1040 Arrays.fill(mCurrentPassword, (byte) 0); 1041 } 1042 if (mFirstPin != null) { 1043 Arrays.fill(mFirstPin, (byte) 0); 1044 } 1045 1046 mPasswordEntry.setText(""); 1047 1048 if (!wasSecureBefore) { 1049 Intent intent = getRedactionInterstitialIntent(getActivity()); 1050 if (intent != null) { 1051 startActivity(intent); 1052 } 1053 } 1054 getActivity().finish(); 1055 } 1056 1057 class TextChangedHandler extends Handler { 1058 private static final int ON_TEXT_CHANGED = 1; 1059 private static final int DELAY_IN_MILLISECOND = 100; 1060 1061 /** 1062 * With the introduction of delay, we batch processing the text changed event to reduce 1063 * unnecessary UI updates. 1064 */ notifyAfterTextChanged()1065 private void notifyAfterTextChanged() { 1066 removeMessages(ON_TEXT_CHANGED); 1067 sendEmptyMessageDelayed(ON_TEXT_CHANGED, DELAY_IN_MILLISECOND); 1068 } 1069 1070 @Override handleMessage(Message msg)1071 public void handleMessage(Message msg) { 1072 if (getActivity() == null) { 1073 return; 1074 } 1075 if (msg.what == ON_TEXT_CHANGED) { 1076 updateUi(); 1077 } 1078 } 1079 } 1080 } 1081 1082 public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase { 1083 1084 private byte[] mChosenPassword; 1085 private byte[] mCurrentPassword; 1086 private int mRequestedQuality; 1087 start(LockPatternUtils utils, boolean required, boolean hasChallenge, long challenge, byte[] chosenPassword, byte[] currentPassword, int requestedQuality, int userId)1088 public void start(LockPatternUtils utils, boolean required, 1089 boolean hasChallenge, long challenge, 1090 byte[] chosenPassword, byte[] currentPassword, int requestedQuality, int userId) { 1091 prepare(utils, required, hasChallenge, challenge, userId); 1092 1093 mChosenPassword = chosenPassword; 1094 mCurrentPassword = currentPassword; 1095 mRequestedQuality = requestedQuality; 1096 mUserId = userId; 1097 1098 start(); 1099 } 1100 1101 @Override saveAndVerifyInBackground()1102 protected Pair<Boolean, Intent> saveAndVerifyInBackground() { 1103 final boolean success = mUtils.saveLockPassword( 1104 mChosenPassword, mCurrentPassword, mRequestedQuality, mUserId); 1105 Intent result = null; 1106 if (success && mHasChallenge) { 1107 byte[] token; 1108 try { 1109 token = mUtils.verifyPassword(mChosenPassword, mChallenge, mUserId); 1110 } catch (RequestThrottledException e) { 1111 token = null; 1112 } 1113 1114 if (token == null) { 1115 Log.e(TAG, "critical: no token returned for known good password."); 1116 } 1117 1118 result = new Intent(); 1119 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); 1120 } 1121 return Pair.create(success, result); 1122 } 1123 } 1124 } 1125