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