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_NUMERIC; 21 22 import static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS; 23 import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE; 24 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_DIGITS; 25 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LETTERS; 26 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LOWER_CASE; 27 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_DIGITS; 28 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_LETTER; 29 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_SYMBOLS; 30 import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_UPPER_CASE; 31 import static com.android.internal.widget.PasswordValidationError.RECENTLY_USED; 32 import static com.android.internal.widget.PasswordValidationError.TOO_LONG; 33 import static com.android.internal.widget.PasswordValidationError.TOO_SHORT; 34 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY; 35 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL; 36 import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_ID; 37 38 import android.app.Activity; 39 import android.app.admin.DevicePolicyManager; 40 import android.app.admin.DevicePolicyManager.PasswordComplexity; 41 import android.app.admin.PasswordMetrics; 42 import android.app.settings.SettingsEnums; 43 import android.content.Context; 44 import android.content.Intent; 45 import android.content.res.Resources.Theme; 46 import android.graphics.Insets; 47 import android.graphics.Typeface; 48 import android.os.Bundle; 49 import android.os.Handler; 50 import android.os.Message; 51 import android.os.UserHandle; 52 import android.text.Editable; 53 import android.text.InputType; 54 import android.text.Selection; 55 import android.text.Spannable; 56 import android.text.TextUtils; 57 import android.text.TextWatcher; 58 import android.util.Log; 59 import android.util.Pair; 60 import android.view.KeyEvent; 61 import android.view.LayoutInflater; 62 import android.view.View; 63 import android.view.ViewGroup; 64 import android.view.inputmethod.EditorInfo; 65 import android.widget.ImeAwareEditText; 66 import android.widget.TextView; 67 import android.widget.TextView.OnEditorActionListener; 68 69 import androidx.annotation.StringRes; 70 import androidx.fragment.app.Fragment; 71 import androidx.recyclerview.widget.LinearLayoutManager; 72 import androidx.recyclerview.widget.RecyclerView; 73 74 import com.android.internal.annotations.VisibleForTesting; 75 import com.android.internal.widget.LockPatternUtils; 76 import com.android.internal.widget.LockPatternUtils.RequestThrottledException; 77 import com.android.internal.widget.LockscreenCredential; 78 import com.android.internal.widget.PasswordValidationError; 79 import com.android.internal.widget.TextViewInputDisabler; 80 import com.android.settings.EncryptionInterstitial; 81 import com.android.settings.R; 82 import com.android.settings.SettingsActivity; 83 import com.android.settings.SetupWizardUtils; 84 import com.android.settings.Utils; 85 import com.android.settings.core.InstrumentedFragment; 86 import com.android.settings.notification.RedactionInterstitial; 87 88 import com.google.android.setupcompat.template.FooterBarMixin; 89 import com.google.android.setupcompat.template.FooterButton; 90 import com.google.android.setupdesign.GlifLayout; 91 92 import java.util.ArrayList; 93 import java.util.Collections; 94 import java.util.List; 95 96 public class ChooseLockPassword extends SettingsActivity { 97 private static final String TAG = "ChooseLockPassword"; 98 99 @Override getIntent()100 public Intent getIntent() { 101 Intent modIntent = new Intent(super.getIntent()); 102 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName()); 103 return modIntent; 104 } 105 106 @Override onApplyThemeResource(Theme theme, int resid, boolean first)107 protected void onApplyThemeResource(Theme theme, int resid, boolean first) { 108 resid = SetupWizardUtils.getTheme(getIntent()); 109 super.onApplyThemeResource(theme, resid, first); 110 } 111 112 public static class IntentBuilder { 113 114 private final Intent mIntent; 115 IntentBuilder(Context context)116 public IntentBuilder(Context context) { 117 mIntent = new Intent(context, ChooseLockPassword.class); 118 mIntent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, false); 119 mIntent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, false); 120 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 121 } 122 setPasswordQuality(int quality)123 public IntentBuilder setPasswordQuality(int quality) { 124 mIntent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality); 125 return this; 126 } 127 setUserId(int userId)128 public IntentBuilder setUserId(int userId) { 129 mIntent.putExtra(Intent.EXTRA_USER_ID, userId); 130 return this; 131 } 132 setChallenge(long challenge)133 public IntentBuilder setChallenge(long challenge) { 134 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true); 135 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge); 136 return this; 137 } 138 setPassword(LockscreenCredential password)139 public IntentBuilder setPassword(LockscreenCredential password) { 140 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, password); 141 return this; 142 } 143 setForFingerprint(boolean forFingerprint)144 public IntentBuilder setForFingerprint(boolean forFingerprint) { 145 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, forFingerprint); 146 return this; 147 } 148 setForFace(boolean forFace)149 public IntentBuilder setForFace(boolean forFace) { 150 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, forFace); 151 return this; 152 } 153 setRequestedMinComplexity(@asswordComplexity int level)154 public IntentBuilder setRequestedMinComplexity(@PasswordComplexity int level) { 155 mIntent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, level); 156 return this; 157 } 158 159 /** 160 * Configures the launch such that at the end of the password enrollment, one of its 161 * managed profile (specified by {@code profileId}) will have its lockscreen unified 162 * to the parent user. The profile's current lockscreen credential needs to be specified by 163 * {@code credential}. 164 */ setProfileToUnify(int profileId, LockscreenCredential credential)165 public IntentBuilder setProfileToUnify(int profileId, LockscreenCredential credential) { 166 mIntent.putExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID, profileId); 167 mIntent.putExtra(EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL, credential); 168 return this; 169 } 170 build()171 public Intent build() { 172 return mIntent; 173 } 174 } 175 176 @Override isValidFragment(String fragmentName)177 protected boolean isValidFragment(String fragmentName) { 178 if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true; 179 return false; 180 } 181 getFragmentClass()182 /* package */ Class<? extends Fragment> getFragmentClass() { 183 return ChooseLockPasswordFragment.class; 184 } 185 186 @Override onCreate(Bundle savedInstanceState)187 protected void onCreate(Bundle savedInstanceState) { 188 super.onCreate(savedInstanceState); 189 final boolean forFingerprint = getIntent() 190 .getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false); 191 final boolean forFace = getIntent() 192 .getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false); 193 194 CharSequence msg = getText(R.string.lockpassword_choose_your_screen_lock_header); 195 if (forFingerprint) { 196 msg = getText(R.string.lockpassword_choose_your_password_header_for_fingerprint); 197 } else if (forFace) { 198 msg = getText(R.string.lockpassword_choose_your_password_header_for_face); 199 } 200 201 setTitle(msg); 202 findViewById(R.id.content_parent).setFitsSystemWindows(false); 203 } 204 205 public static class ChooseLockPasswordFragment extends InstrumentedFragment 206 implements OnEditorActionListener, TextWatcher, SaveAndFinishWorker.Listener { 207 private static final String KEY_FIRST_PASSWORD = "first_password"; 208 private static final String KEY_UI_STAGE = "ui_stage"; 209 private static final String KEY_CURRENT_CREDENTIAL = "current_credential"; 210 private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker"; 211 212 private LockscreenCredential mCurrentCredential; 213 private LockscreenCredential mChosenPassword; 214 private boolean mHasChallenge; 215 private long mChallenge; 216 private ImeAwareEditText mPasswordEntry; 217 private TextViewInputDisabler mPasswordEntryInputDisabler; 218 219 // Minimum password metrics enforced by admins. 220 private PasswordMetrics mMinMetrics; 221 private List<PasswordValidationError> mValidationErrors; 222 223 @PasswordComplexity private int mMinComplexity = PASSWORD_COMPLEXITY_NONE; 224 protected int mUserId; 225 private byte[] mPasswordHistoryHashFactor; 226 private int mUnificationProfileId = UserHandle.USER_NULL; 227 228 private LockPatternUtils mLockPatternUtils; 229 private SaveAndFinishWorker mSaveAndFinishWorker; 230 private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 231 private ChooseLockSettingsHelper mChooseLockSettingsHelper; 232 protected Stage mUiStage = Stage.Introduction; 233 private PasswordRequirementAdapter mPasswordRequirementAdapter; 234 private GlifLayout mLayout; 235 protected boolean mForFingerprint; 236 protected boolean mForFace; 237 238 private LockscreenCredential mFirstPassword; 239 private RecyclerView mPasswordRestrictionView; 240 protected boolean mIsAlphaMode; 241 protected FooterButton mSkipOrClearButton; 242 private FooterButton mNextButton; 243 private TextView mMessage; 244 245 private TextChangedHandler mTextChangedHandler; 246 247 private static final int CONFIRM_EXISTING_REQUEST = 58; 248 static final int RESULT_FINISHED = RESULT_FIRST_USER; 249 250 /** 251 * Keep track internally of where the user is in choosing a pattern. 252 */ 253 protected enum Stage { 254 255 Introduction( 256 R.string.lockpassword_choose_your_screen_lock_header, // password 257 R.string.lockpassword_choose_your_password_header_for_fingerprint, 258 R.string.lockpassword_choose_your_password_header_for_face, 259 R.string.lockpassword_choose_your_screen_lock_header, // pin 260 R.string.lockpassword_choose_your_pin_header_for_fingerprint, 261 R.string.lockpassword_choose_your_pin_header_for_face, 262 R.string.lockpassword_choose_your_password_message, // added security message 263 R.string.lock_settings_picker_biometrics_added_security_message, 264 R.string.lockpassword_choose_your_pin_message, 265 R.string.lock_settings_picker_biometrics_added_security_message, 266 R.string.next_label), 267 268 NeedToConfirm( 269 R.string.lockpassword_confirm_your_password_header, 270 R.string.lockpassword_confirm_your_password_header, 271 R.string.lockpassword_confirm_your_password_header, 272 R.string.lockpassword_confirm_your_pin_header, 273 R.string.lockpassword_confirm_your_pin_header, 274 R.string.lockpassword_confirm_your_pin_header, 275 0, 276 0, 277 0, 278 0, 279 R.string.lockpassword_confirm_label), 280 281 ConfirmWrong( 282 R.string.lockpassword_confirm_passwords_dont_match, 283 R.string.lockpassword_confirm_passwords_dont_match, 284 R.string.lockpassword_confirm_passwords_dont_match, 285 R.string.lockpassword_confirm_pins_dont_match, 286 R.string.lockpassword_confirm_pins_dont_match, 287 R.string.lockpassword_confirm_pins_dont_match, 288 0, 289 0, 290 0, 291 0, 292 R.string.lockpassword_confirm_label); 293 Stage(int hintInAlpha, int hintInAlphaForFingerprint, int hintInAlphaForFace, int hintInNumeric, int hintInNumericForFingerprint, int hintInNumericForFace, int messageInAlpha, int messageInAlphaForBiometrics, int messageInNumeric, int messageInNumericForBiometrics, int nextButtonText)294 Stage(int hintInAlpha, int hintInAlphaForFingerprint, int hintInAlphaForFace, 295 int hintInNumeric, int hintInNumericForFingerprint, int hintInNumericForFace, 296 int messageInAlpha, int messageInAlphaForBiometrics, 297 int messageInNumeric, int messageInNumericForBiometrics, 298 int nextButtonText) { 299 this.alphaHint = hintInAlpha; 300 this.alphaHintForFingerprint = hintInAlphaForFingerprint; 301 this.alphaHintForFace = hintInAlphaForFace; 302 303 this.numericHint = hintInNumeric; 304 this.numericHintForFingerprint = hintInNumericForFingerprint; 305 this.numericHintForFace = hintInNumericForFace; 306 307 this.alphaMessage = messageInAlpha; 308 this.alphaMessageForBiometrics = messageInAlphaForBiometrics; 309 this.numericMessage = messageInNumeric; 310 this.numericMessageForBiometrics = messageInNumericForBiometrics; 311 this.buttonText = nextButtonText; 312 } 313 314 public static final int TYPE_NONE = 0; 315 public static final int TYPE_FINGERPRINT = 1; 316 public static final int TYPE_FACE = 2; 317 318 // Password 319 public final int alphaHint; 320 public final int alphaHintForFingerprint; 321 public final int alphaHintForFace; 322 323 // PIN 324 public final int numericHint; 325 public final int numericHintForFingerprint; 326 public final int numericHintForFace; 327 328 public final int alphaMessage; 329 public final int alphaMessageForBiometrics; 330 public final int numericMessage; 331 public final int numericMessageForBiometrics; 332 public final int buttonText; 333 getHint(boolean isAlpha, int type)334 public @StringRes int getHint(boolean isAlpha, int type) { 335 if (isAlpha) { 336 if (type == TYPE_FINGERPRINT) { 337 return alphaHintForFingerprint; 338 } else if (type == TYPE_FACE) { 339 return alphaHintForFace; 340 } else { 341 return alphaHint; 342 } 343 } else { 344 if (type == TYPE_FINGERPRINT) { 345 return numericHintForFingerprint; 346 } else if (type == TYPE_FACE) { 347 return numericHintForFace; 348 } else { 349 return numericHint; 350 } 351 } 352 } 353 getMessage(boolean isAlpha, int type)354 public @StringRes int getMessage(boolean isAlpha, int type) { 355 if (isAlpha) { 356 return type != TYPE_NONE ? alphaMessageForBiometrics : alphaMessage; 357 } else { 358 return type != TYPE_NONE ? numericMessageForBiometrics : numericMessage; 359 } 360 } 361 } 362 363 // required constructor for fragments ChooseLockPasswordFragment()364 public ChooseLockPasswordFragment() { 365 366 } 367 368 @Override onCreate(Bundle savedInstanceState)369 public void onCreate(Bundle savedInstanceState) { 370 super.onCreate(savedInstanceState); 371 mLockPatternUtils = new LockPatternUtils(getActivity()); 372 Intent intent = getActivity().getIntent(); 373 if (!(getActivity() instanceof ChooseLockPassword)) { 374 throw new SecurityException("Fragment contained in wrong activity"); 375 } 376 // Only take this argument into account if it belongs to the current profile. 377 mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras()); 378 mForFingerprint = intent.getBooleanExtra( 379 ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false); 380 mForFace = intent.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false); 381 mMinComplexity = intent.getIntExtra( 382 EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE); 383 384 mRequestedQuality = intent.getIntExtra( 385 LockPatternUtils.PASSWORD_TYPE_KEY, PASSWORD_QUALITY_NUMERIC); 386 mUnificationProfileId = intent.getIntExtra( 387 EXTRA_KEY_UNIFICATION_PROFILE_ID, UserHandle.USER_NULL); 388 389 mMinMetrics = mLockPatternUtils.getRequestedPasswordMetrics(mUserId); 390 // If we are to unify a work challenge at the end of the credential enrollment, manually 391 // merge any password policy from that profile here, so we are enrolling a compliant 392 // password. This is because once unified, the profile's password policy will 393 // be enforced on the new credential. 394 if (mUnificationProfileId != UserHandle.USER_NULL) { 395 mMinMetrics.maxWith( 396 mLockPatternUtils.getRequestedPasswordMetrics(mUnificationProfileId)); 397 } 398 399 mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity()); 400 401 if (intent.getBooleanExtra( 402 ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) { 403 SaveAndFinishWorker w = new SaveAndFinishWorker(); 404 final boolean required = getActivity().getIntent().getBooleanExtra( 405 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 406 LockscreenCredential currentCredential = intent.getParcelableExtra( 407 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 408 409 w.setBlocking(true); 410 w.setListener(this); 411 w.start(mChooseLockSettingsHelper.utils(), required, false, 0, 412 currentCredential, currentCredential, mUserId); 413 } 414 mTextChangedHandler = new TextChangedHandler(); 415 } 416 417 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)418 public View onCreateView(LayoutInflater inflater, ViewGroup container, 419 Bundle savedInstanceState) { 420 return inflater.inflate(R.layout.choose_lock_password, container, false); 421 } 422 423 @Override onViewCreated(View view, Bundle savedInstanceState)424 public void onViewCreated(View view, Bundle savedInstanceState) { 425 super.onViewCreated(view, savedInstanceState); 426 427 mLayout = (GlifLayout) view; 428 429 // Make the password container consume the optical insets so the edit text is aligned 430 // with the sides of the parent visually. 431 ViewGroup container = view.findViewById(R.id.password_container); 432 container.setOpticalInsets(Insets.NONE); 433 434 final FooterBarMixin mixin = mLayout.getMixin(FooterBarMixin.class); 435 mixin.setSecondaryButton( 436 new FooterButton.Builder(getActivity()) 437 .setText(R.string.lockpassword_clear_label) 438 .setListener(this::onSkipOrClearButtonClick) 439 .setButtonType(FooterButton.ButtonType.SKIP) 440 .setTheme(R.style.SudGlifButton_Secondary) 441 .build() 442 ); 443 mixin.setPrimaryButton( 444 new FooterButton.Builder(getActivity()) 445 .setText(R.string.next_label) 446 .setListener(this::onNextButtonClick) 447 .setButtonType(FooterButton.ButtonType.NEXT) 448 .setTheme(R.style.SudGlifButton_Primary) 449 .build() 450 ); 451 mSkipOrClearButton = mixin.getSecondaryButton(); 452 mNextButton = mixin.getPrimaryButton(); 453 454 mMessage = view.findViewById(R.id.sud_layout_description); 455 if (mForFingerprint) { 456 mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_fingerprint_header)); 457 } else if (mForFace) { 458 mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_face_header)); 459 } 460 461 mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality 462 || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality 463 || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality; 464 465 setupPasswordRequirementsView(view); 466 467 mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); 468 mPasswordEntry = view.findViewById(R.id.password_entry); 469 mPasswordEntry.setOnEditorActionListener(this); 470 mPasswordEntry.addTextChangedListener(this); 471 mPasswordEntry.requestFocus(); 472 mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry); 473 474 final Activity activity = getActivity(); 475 476 int currentType = mPasswordEntry.getInputType(); 477 mPasswordEntry.setInputType(mIsAlphaMode ? currentType 478 : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)); 479 if (mIsAlphaMode) { 480 mPasswordEntry.setContentDescription( 481 getString(R.string.unlock_set_unlock_password_title)); 482 } else { 483 mPasswordEntry.setContentDescription( 484 getString(R.string.unlock_set_unlock_pin_title)); 485 } 486 // Can't set via XML since setInputType resets the fontFamily to null 487 mPasswordEntry.setTypeface(Typeface.create( 488 getContext().getString(com.android.internal.R.string.config_headlineFontFamily), 489 Typeface.NORMAL)); 490 491 Intent intent = getActivity().getIntent(); 492 final boolean confirmCredentials = intent.getBooleanExtra( 493 ChooseLockGeneric.CONFIRM_CREDENTIALS, true); 494 mCurrentCredential = intent.getParcelableExtra( 495 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 496 mHasChallenge = intent.getBooleanExtra( 497 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 498 mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0); 499 if (savedInstanceState == null) { 500 updateStage(Stage.Introduction); 501 if (confirmCredentials) { 502 mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, 503 getString(R.string.unlock_set_unlock_launch_picker_title), true, 504 mUserId); 505 } 506 } else { 507 508 // restore from previous state 509 mFirstPassword = savedInstanceState.getParcelable(KEY_FIRST_PASSWORD); 510 final String state = savedInstanceState.getString(KEY_UI_STAGE); 511 if (state != null) { 512 mUiStage = Stage.valueOf(state); 513 updateStage(mUiStage); 514 } 515 516 if (mCurrentCredential == null) { 517 mCurrentCredential = savedInstanceState.getParcelable(KEY_CURRENT_CREDENTIAL); 518 } 519 520 // Re-attach to the exiting worker if there is one. 521 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag( 522 FRAGMENT_TAG_SAVE_AND_FINISH); 523 } 524 525 if (activity instanceof SettingsActivity) { 526 final SettingsActivity sa = (SettingsActivity) activity; 527 int title = Stage.Introduction.getHint(mIsAlphaMode, getStageType()); 528 sa.setTitle(title); 529 mLayout.setHeaderText(title); 530 } 531 } 532 533 @Override onDestroy()534 public void onDestroy() { 535 super.onDestroy(); 536 if (mCurrentCredential != null) { 537 mCurrentCredential.zeroize(); 538 } 539 // Force a garbage collection immediately to remove remnant of user password shards 540 // from memory. 541 System.gc(); 542 System.runFinalization(); 543 System.gc(); 544 } 545 getStageType()546 protected int getStageType() { 547 return mForFingerprint ? Stage.TYPE_FINGERPRINT : 548 mForFace ? Stage.TYPE_FACE : 549 Stage.TYPE_NONE; 550 } 551 setupPasswordRequirementsView(View view)552 private void setupPasswordRequirementsView(View view) { 553 mPasswordRestrictionView = view.findViewById(R.id.password_requirements_view); 554 mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); 555 mPasswordRequirementAdapter = new PasswordRequirementAdapter(); 556 mPasswordRestrictionView.setAdapter(mPasswordRequirementAdapter); 557 } 558 559 @Override getMetricsCategory()560 public int getMetricsCategory() { 561 return SettingsEnums.CHOOSE_LOCK_PASSWORD; 562 } 563 564 @Override onResume()565 public void onResume() { 566 super.onResume(); 567 updateStage(mUiStage); 568 if (mSaveAndFinishWorker != null) { 569 mSaveAndFinishWorker.setListener(this); 570 } else { 571 mPasswordEntry.requestFocus(); 572 mPasswordEntry.scheduleShowSoftInput(); 573 } 574 } 575 576 @Override onPause()577 public void onPause() { 578 if (mSaveAndFinishWorker != null) { 579 mSaveAndFinishWorker.setListener(null); 580 } 581 super.onPause(); 582 } 583 584 @Override onSaveInstanceState(Bundle outState)585 public void onSaveInstanceState(Bundle outState) { 586 super.onSaveInstanceState(outState); 587 outState.putString(KEY_UI_STAGE, mUiStage.name()); 588 outState.putParcelable(KEY_FIRST_PASSWORD, mFirstPassword); 589 outState.putParcelable(KEY_CURRENT_CREDENTIAL, mCurrentCredential); 590 } 591 592 @Override onActivityResult(int requestCode, int resultCode, Intent data)593 public void onActivityResult(int requestCode, int resultCode, 594 Intent data) { 595 super.onActivityResult(requestCode, resultCode, data); 596 switch (requestCode) { 597 case CONFIRM_EXISTING_REQUEST: 598 if (resultCode != Activity.RESULT_OK) { 599 getActivity().setResult(RESULT_FINISHED); 600 getActivity().finish(); 601 } else { 602 mCurrentCredential = data.getParcelableExtra( 603 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 604 } 605 break; 606 } 607 } 608 getRedactionInterstitialIntent(Context context)609 protected Intent getRedactionInterstitialIntent(Context context) { 610 return RedactionInterstitial.createStartIntent(context, mUserId); 611 } 612 updateStage(Stage stage)613 protected void updateStage(Stage stage) { 614 final Stage previousStage = mUiStage; 615 mUiStage = stage; 616 updateUi(); 617 618 // If the stage changed, announce the header for accessibility. This 619 // is a no-op when accessibility is disabled. 620 if (previousStage != stage) { 621 mLayout.announceForAccessibility(mLayout.getHeaderText()); 622 } 623 } 624 625 /** 626 * Validates PIN/Password and returns the validation result and updates mValidationErrors 627 * and mPasswordReused to reflect validation results. 628 * 629 * @param credential credential the user typed in. 630 * @return whether password satisfies all the requirements. 631 */ 632 @VisibleForTesting validatePassword(LockscreenCredential credential)633 boolean validatePassword(LockscreenCredential credential) { 634 final byte[] password = credential.getCredential(); 635 mValidationErrors = PasswordMetrics.validatePassword( 636 mMinMetrics, mMinComplexity, !mIsAlphaMode, password); 637 if (mValidationErrors.isEmpty() && mLockPatternUtils.checkPasswordHistory( 638 password, getPasswordHistoryHashFactor(), mUserId)) { 639 mValidationErrors = 640 Collections.singletonList(new PasswordValidationError(RECENTLY_USED)); 641 } 642 return mValidationErrors.isEmpty(); 643 } 644 645 /** 646 * Lazily compute and return the history hash factor of the current user (mUserId), used for 647 * password history check. 648 */ getPasswordHistoryHashFactor()649 private byte[] getPasswordHistoryHashFactor() { 650 if (mPasswordHistoryHashFactor == null) { 651 mPasswordHistoryHashFactor = mLockPatternUtils.getPasswordHistoryHashFactor( 652 mCurrentCredential != null ? mCurrentCredential 653 : LockscreenCredential.createNone(), mUserId); 654 } 655 return mPasswordHistoryHashFactor; 656 } 657 handleNext()658 public void handleNext() { 659 if (mSaveAndFinishWorker != null) return; 660 // TODO(b/120484642): This is a point of entry for passwords from the UI 661 final Editable passwordText = mPasswordEntry.getText(); 662 if (TextUtils.isEmpty(passwordText)) { 663 return; 664 } 665 mChosenPassword = mIsAlphaMode ? LockscreenCredential.createPassword(passwordText) 666 : LockscreenCredential.createPin(passwordText); 667 if (mUiStage == Stage.Introduction) { 668 if (validatePassword(mChosenPassword)) { 669 mFirstPassword = mChosenPassword; 670 mPasswordEntry.setText(""); 671 updateStage(Stage.NeedToConfirm); 672 } else { 673 mChosenPassword.zeroize(); 674 } 675 } else if (mUiStage == Stage.NeedToConfirm) { 676 if (mChosenPassword.equals(mFirstPassword)) { 677 startSaveAndFinish(); 678 } else { 679 CharSequence tmp = mPasswordEntry.getText(); 680 if (tmp != null) { 681 Selection.setSelection((Spannable) tmp, 0, tmp.length()); 682 } 683 updateStage(Stage.ConfirmWrong); 684 mChosenPassword.zeroize(); 685 } 686 } 687 } 688 setNextEnabled(boolean enabled)689 protected void setNextEnabled(boolean enabled) { 690 mNextButton.setEnabled(enabled); 691 } 692 setNextText(int text)693 protected void setNextText(int text) { 694 mNextButton.setText(getActivity(), text); 695 } 696 onSkipOrClearButtonClick(View view)697 protected void onSkipOrClearButtonClick(View view) { 698 mPasswordEntry.setText(""); 699 } 700 onNextButtonClick(View view)701 protected void onNextButtonClick(View view) { 702 handleNext(); 703 } 704 onEditorAction(TextView v, int actionId, KeyEvent event)705 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 706 // Check if this was the result of hitting the enter or "done" key 707 if (actionId == EditorInfo.IME_NULL 708 || actionId == EditorInfo.IME_ACTION_DONE 709 || actionId == EditorInfo.IME_ACTION_NEXT) { 710 handleNext(); 711 return true; 712 } 713 return false; 714 } 715 716 /** 717 * @param errorCode error code returned from password validation. 718 * @return an array of messages describing the error, important messages come first. 719 */ convertErrorCodeToMessages()720 String[] convertErrorCodeToMessages() { 721 List<String> messages = new ArrayList<>(); 722 for (PasswordValidationError error : mValidationErrors) { 723 switch (error.errorCode) { 724 case CONTAINS_INVALID_CHARACTERS: 725 messages.add(getString(R.string.lockpassword_illegal_character)); 726 break; 727 case NOT_ENOUGH_UPPER_CASE: 728 messages.add(getResources().getQuantityString( 729 R.plurals.lockpassword_password_requires_uppercase, 730 error.requirement, error.requirement)); 731 break; 732 case NOT_ENOUGH_LOWER_CASE: 733 messages.add(getResources().getQuantityString( 734 R.plurals.lockpassword_password_requires_lowercase, 735 error.requirement, error.requirement)); 736 break; 737 case NOT_ENOUGH_LETTERS: 738 messages.add(getResources().getQuantityString( 739 R.plurals.lockpassword_password_requires_letters, 740 error.requirement, error.requirement)); 741 break; 742 case NOT_ENOUGH_DIGITS: 743 messages.add(getResources().getQuantityString( 744 R.plurals.lockpassword_password_requires_numeric, 745 error.requirement, error.requirement)); 746 break; 747 case NOT_ENOUGH_SYMBOLS: 748 messages.add(getResources().getQuantityString( 749 R.plurals.lockpassword_password_requires_symbols, 750 error.requirement, error.requirement)); 751 break; 752 case NOT_ENOUGH_NON_LETTER: 753 messages.add(getResources().getQuantityString( 754 R.plurals.lockpassword_password_requires_nonletter, 755 error.requirement, error.requirement)); 756 break; 757 case NOT_ENOUGH_NON_DIGITS: 758 messages.add(getResources().getQuantityString( 759 R.plurals.lockpassword_password_requires_nonnumerical, 760 error.requirement, error.requirement)); 761 break; 762 case TOO_SHORT: 763 messages.add(getResources().getQuantityString( 764 mIsAlphaMode 765 ? R.plurals.lockpassword_password_too_short 766 : R.plurals.lockpassword_pin_too_short, 767 error.requirement, error.requirement)); 768 break; 769 case TOO_LONG: 770 messages.add(getResources().getQuantityString( 771 mIsAlphaMode 772 ? R.plurals.lockpassword_password_too_long 773 : R.plurals.lockpassword_pin_too_long, 774 error.requirement + 1, error.requirement + 1)); 775 break; 776 case CONTAINS_SEQUENCE: 777 messages.add(getString(R.string.lockpassword_pin_no_sequential_digits)); 778 break; 779 case RECENTLY_USED: 780 messages.add(getString(mIsAlphaMode 781 ? R.string.lockpassword_password_recently_used 782 : R.string.lockpassword_pin_recently_used)); 783 break; 784 default: 785 Log.wtf(TAG, "unknown error validating password: " + error); 786 } 787 } 788 789 return messages.toArray(new String[0]); 790 } 791 792 /** 793 * Update the hint based on current Stage and length of password entry 794 */ updateUi()795 protected void updateUi() { 796 final boolean canInput = mSaveAndFinishWorker == null; 797 798 LockscreenCredential password = mIsAlphaMode 799 ? LockscreenCredential.createPasswordOrNone(mPasswordEntry.getText()) 800 : LockscreenCredential.createPinOrNone(mPasswordEntry.getText()); 801 final int length = password.size(); 802 if (mUiStage == Stage.Introduction) { 803 mPasswordRestrictionView.setVisibility(View.VISIBLE); 804 final boolean passwordCompliant = validatePassword(password); 805 String[] messages = convertErrorCodeToMessages(); 806 // Update the fulfillment of requirements. 807 mPasswordRequirementAdapter.setRequirements(messages); 808 // Enable/Disable the next button accordingly. 809 setNextEnabled(passwordCompliant); 810 } else { 811 // Hide password requirement view when we are just asking user to confirm the pw. 812 mPasswordRestrictionView.setVisibility(View.GONE); 813 setHeaderText(getString(mUiStage.getHint(mIsAlphaMode, getStageType()))); 814 setNextEnabled(canInput && length >= LockPatternUtils.MIN_LOCK_PASSWORD_SIZE); 815 mSkipOrClearButton.setVisibility(toVisibility(canInput && length > 0)); 816 } 817 int message = mUiStage.getMessage(mIsAlphaMode, getStageType()); 818 if (message != 0) { 819 mMessage.setVisibility(View.VISIBLE); 820 mMessage.setText(message); 821 } else { 822 mMessage.setVisibility(View.INVISIBLE); 823 } 824 825 setNextText(mUiStage.buttonText); 826 mPasswordEntryInputDisabler.setInputEnabled(canInput); 827 password.zeroize(); 828 } 829 toVisibility(boolean visibleOrGone)830 protected int toVisibility(boolean visibleOrGone) { 831 return visibleOrGone ? View.VISIBLE : View.GONE; 832 } 833 setHeaderText(String text)834 private void setHeaderText(String text) { 835 // Only set the text if it is different than the existing one to avoid announcing again. 836 if (!TextUtils.isEmpty(mLayout.getHeaderText()) 837 && mLayout.getHeaderText().toString().equals(text)) { 838 return; 839 } 840 mLayout.setHeaderText(text); 841 } 842 afterTextChanged(Editable s)843 public void afterTextChanged(Editable s) { 844 // Changing the text while error displayed resets to NeedToConfirm state 845 if (mUiStage == Stage.ConfirmWrong) { 846 mUiStage = Stage.NeedToConfirm; 847 } 848 // Schedule the UI update. 849 mTextChangedHandler.notifyAfterTextChanged(); 850 } 851 beforeTextChanged(CharSequence s, int start, int count, int after)852 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 853 854 } 855 onTextChanged(CharSequence s, int start, int before, int count)856 public void onTextChanged(CharSequence s, int start, int before, int count) { 857 858 } 859 startSaveAndFinish()860 private void startSaveAndFinish() { 861 if (mSaveAndFinishWorker != null) { 862 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker."); 863 return; 864 } 865 866 mPasswordEntryInputDisabler.setInputEnabled(false); 867 setNextEnabled(false); 868 869 mSaveAndFinishWorker = new SaveAndFinishWorker(); 870 mSaveAndFinishWorker.setListener(this); 871 872 getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, 873 FRAGMENT_TAG_SAVE_AND_FINISH).commit(); 874 getFragmentManager().executePendingTransactions(); 875 876 final Intent intent = getActivity().getIntent(); 877 final boolean required = intent.getBooleanExtra( 878 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 879 if (mUnificationProfileId != UserHandle.USER_NULL) { 880 try (LockscreenCredential profileCredential = (LockscreenCredential) 881 intent.getParcelableExtra(EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL)) { 882 mSaveAndFinishWorker.setProfileToUnify(mUnificationProfileId, 883 profileCredential); 884 } 885 } 886 mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge, 887 mChosenPassword, mCurrentCredential, mUserId); 888 } 889 890 @Override onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)891 public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { 892 getActivity().setResult(RESULT_FINISHED, resultData); 893 894 if (mChosenPassword != null) { 895 mChosenPassword.zeroize(); 896 } 897 if (mCurrentCredential != null) { 898 mCurrentCredential.zeroize(); 899 } 900 if (mFirstPassword != null) { 901 mFirstPassword.zeroize(); 902 } 903 904 mPasswordEntry.setText(""); 905 906 if (!wasSecureBefore) { 907 Intent intent = getRedactionInterstitialIntent(getActivity()); 908 if (intent != null) { 909 startActivity(intent); 910 } 911 } 912 getActivity().finish(); 913 } 914 915 class TextChangedHandler extends Handler { 916 private static final int ON_TEXT_CHANGED = 1; 917 private static final int DELAY_IN_MILLISECOND = 100; 918 919 /** 920 * With the introduction of delay, we batch processing the text changed event to reduce 921 * unnecessary UI updates. 922 */ notifyAfterTextChanged()923 private void notifyAfterTextChanged() { 924 removeMessages(ON_TEXT_CHANGED); 925 sendEmptyMessageDelayed(ON_TEXT_CHANGED, DELAY_IN_MILLISECOND); 926 } 927 928 @Override handleMessage(Message msg)929 public void handleMessage(Message msg) { 930 if (getActivity() == null) { 931 return; 932 } 933 if (msg.what == ON_TEXT_CHANGED) { 934 updateUi(); 935 } 936 } 937 } 938 } 939 940 public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase { 941 942 private LockscreenCredential mChosenPassword; 943 private LockscreenCredential mCurrentCredential; 944 start(LockPatternUtils utils, boolean required, boolean hasChallenge, long challenge, LockscreenCredential chosenPassword, LockscreenCredential currentCredential, int userId)945 public void start(LockPatternUtils utils, boolean required, 946 boolean hasChallenge, long challenge, 947 LockscreenCredential chosenPassword, LockscreenCredential currentCredential, 948 int userId) { 949 prepare(utils, required, hasChallenge, challenge, userId); 950 951 mChosenPassword = chosenPassword; 952 mCurrentCredential = currentCredential != null ? currentCredential 953 : LockscreenCredential.createNone(); 954 mUserId = userId; 955 956 start(); 957 } 958 959 @Override saveAndVerifyInBackground()960 protected Pair<Boolean, Intent> saveAndVerifyInBackground() { 961 final boolean success = mUtils.setLockCredential( 962 mChosenPassword, mCurrentCredential, mUserId); 963 if (success) { 964 unifyProfileCredentialIfRequested(); 965 } 966 Intent result = null; 967 if (success && mHasChallenge) { 968 byte[] token; 969 try { 970 token = mUtils.verifyCredential(mChosenPassword, mChallenge, mUserId); 971 } catch (RequestThrottledException e) { 972 token = null; 973 } 974 975 if (token == null) { 976 Log.e(TAG, "critical: no token returned for known good password."); 977 } 978 979 result = new Intent(); 980 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); 981 } 982 return Pair.create(success, result); 983 } 984 } 985 } 986