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 if (mForFingerprint) { 503 mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_fingerprint_header)); 504 } else if (mForFace) { 505 mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_face_header)); 506 } else if (mForBiometrics) { 507 mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_lock)); 508 } 509 510 mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mPasswordType 511 || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mPasswordType 512 || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mPasswordType; 513 514 setupPasswordRequirementsView(view); 515 516 mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); 517 mPasswordEntry = view.findViewById(R.id.password_entry); 518 mPasswordEntry.setOnEditorActionListener(this); 519 mPasswordEntry.addTextChangedListener(this); 520 mPasswordEntry.requestFocus(); 521 mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry); 522 523 final Activity activity = getActivity(); 524 525 int currentType = mPasswordEntry.getInputType(); 526 mPasswordEntry.setInputType(mIsAlphaMode ? currentType 527 : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)); 528 if (mIsAlphaMode) { 529 mPasswordEntry.setContentDescription( 530 getString(R.string.unlock_set_unlock_password_title)); 531 } else { 532 mPasswordEntry.setContentDescription( 533 getString(R.string.unlock_set_unlock_pin_title)); 534 } 535 // Can't set via XML since setInputType resets the fontFamily to null 536 mPasswordEntry.setTypeface(Typeface.create( 537 getContext().getString(com.android.internal.R.string.config_headlineFontFamily), 538 Typeface.NORMAL)); 539 540 Intent intent = getActivity().getIntent(); 541 final boolean confirmCredentials = intent.getBooleanExtra( 542 ChooseLockGeneric.CONFIRM_CREDENTIALS, true); 543 mCurrentCredential = intent.getParcelableExtra( 544 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 545 mRequestGatekeeperPassword = intent.getBooleanExtra( 546 ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false); 547 if (savedInstanceState == null) { 548 updateStage(Stage.Introduction); 549 if (confirmCredentials) { 550 final ChooseLockSettingsHelper.Builder builder = 551 new ChooseLockSettingsHelper.Builder(getActivity()); 552 builder.setRequestCode(CONFIRM_EXISTING_REQUEST) 553 .setTitle(getString(R.string.unlock_set_unlock_launch_picker_title)) 554 .setReturnCredentials(true) 555 .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword) 556 .setUserId(mUserId) 557 .show(); 558 } 559 } else { 560 561 // restore from previous state 562 mFirstPassword = savedInstanceState.getParcelable(KEY_FIRST_PASSWORD); 563 final String state = savedInstanceState.getString(KEY_UI_STAGE); 564 if (state != null) { 565 mUiStage = Stage.valueOf(state); 566 updateStage(mUiStage); 567 } 568 569 mCurrentCredential = savedInstanceState.getParcelable(KEY_CURRENT_CREDENTIAL); 570 571 // Re-attach to the exiting worker if there is one. 572 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag( 573 FRAGMENT_TAG_SAVE_AND_FINISH); 574 } 575 576 if (activity instanceof SettingsActivity) { 577 final SettingsActivity sa = (SettingsActivity) activity; 578 int title = Stage.Introduction.getHint(mIsAlphaMode, getStageType(), 579 mIsManagedProfile); 580 sa.setTitle(title); 581 mLayout.setHeaderText(title); 582 } 583 } 584 585 @Override onDestroy()586 public void onDestroy() { 587 super.onDestroy(); 588 if (mCurrentCredential != null) { 589 mCurrentCredential.zeroize(); 590 } 591 // Force a garbage collection immediately to remove remnant of user password shards 592 // from memory. 593 System.gc(); 594 System.runFinalization(); 595 System.gc(); 596 } 597 getStageType()598 protected int getStageType() { 599 if (mForFingerprint) { 600 return Stage.TYPE_FINGERPRINT; 601 } else if (mForFace) { 602 return Stage.TYPE_FACE; 603 } else if (mForBiometrics) { 604 return Stage.TYPE_BIOMETRIC; 605 } else { 606 return Stage.TYPE_NONE; 607 } 608 } 609 setupPasswordRequirementsView(View view)610 private void setupPasswordRequirementsView(View view) { 611 mPasswordRestrictionView = view.findViewById(R.id.password_requirements_view); 612 mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); 613 mPasswordRequirementAdapter = new PasswordRequirementAdapter(); 614 mPasswordRestrictionView.setAdapter(mPasswordRequirementAdapter); 615 } 616 617 @Override getMetricsCategory()618 public int getMetricsCategory() { 619 return SettingsEnums.CHOOSE_LOCK_PASSWORD; 620 } 621 622 @Override onResume()623 public void onResume() { 624 super.onResume(); 625 updateStage(mUiStage); 626 if (mSaveAndFinishWorker != null) { 627 mSaveAndFinishWorker.setListener(this); 628 } else { 629 mPasswordEntry.requestFocus(); 630 mPasswordEntry.scheduleShowSoftInput(); 631 } 632 } 633 634 @Override onPause()635 public void onPause() { 636 if (mSaveAndFinishWorker != null) { 637 mSaveAndFinishWorker.setListener(null); 638 } 639 super.onPause(); 640 } 641 642 @Override onSaveInstanceState(Bundle outState)643 public void onSaveInstanceState(Bundle outState) { 644 super.onSaveInstanceState(outState); 645 outState.putString(KEY_UI_STAGE, mUiStage.name()); 646 outState.putParcelable(KEY_FIRST_PASSWORD, mFirstPassword); 647 if (mCurrentCredential != null) { 648 outState.putParcelable(KEY_CURRENT_CREDENTIAL, mCurrentCredential.duplicate()); 649 } 650 } 651 652 @Override onActivityResult(int requestCode, int resultCode, Intent data)653 public void onActivityResult(int requestCode, int resultCode, 654 Intent data) { 655 super.onActivityResult(requestCode, resultCode, data); 656 switch (requestCode) { 657 case CONFIRM_EXISTING_REQUEST: 658 if (resultCode != Activity.RESULT_OK) { 659 getActivity().setResult(RESULT_FINISHED); 660 getActivity().finish(); 661 } else { 662 mCurrentCredential = data.getParcelableExtra( 663 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 664 } 665 break; 666 } 667 } 668 getRedactionInterstitialIntent(Context context)669 protected Intent getRedactionInterstitialIntent(Context context) { 670 return RedactionInterstitial.createStartIntent(context, mUserId); 671 } 672 updateStage(Stage stage)673 protected void updateStage(Stage stage) { 674 final Stage previousStage = mUiStage; 675 mUiStage = stage; 676 updateUi(); 677 678 // If the stage changed, announce the header for accessibility. This 679 // is a no-op when accessibility is disabled. 680 if (previousStage != stage) { 681 mLayout.announceForAccessibility(mLayout.getHeaderText()); 682 } 683 } 684 685 /** 686 * Validates PIN/Password and returns the validation result and updates mValidationErrors 687 * and mPasswordReused to reflect validation results. 688 * 689 * @param credential credential the user typed in. 690 * @return whether password satisfies all the requirements. 691 */ 692 @VisibleForTesting validatePassword(LockscreenCredential credential)693 boolean validatePassword(LockscreenCredential credential) { 694 final byte[] password = credential.getCredential(); 695 mValidationErrors = PasswordMetrics.validatePassword( 696 mMinMetrics, mMinComplexity, !mIsAlphaMode, password); 697 if (mValidationErrors.isEmpty() && mLockPatternUtils.checkPasswordHistory( 698 password, getPasswordHistoryHashFactor(), mUserId)) { 699 mValidationErrors = 700 Collections.singletonList(new PasswordValidationError(RECENTLY_USED)); 701 } 702 return mValidationErrors.isEmpty(); 703 } 704 705 /** 706 * Lazily compute and return the history hash factor of the current user (mUserId), used for 707 * password history check. 708 */ getPasswordHistoryHashFactor()709 private byte[] getPasswordHistoryHashFactor() { 710 if (mPasswordHistoryHashFactor == null) { 711 mPasswordHistoryHashFactor = mLockPatternUtils.getPasswordHistoryHashFactor( 712 mCurrentCredential != null ? mCurrentCredential 713 : LockscreenCredential.createNone(), mUserId); 714 } 715 return mPasswordHistoryHashFactor; 716 } 717 handleNext()718 public void handleNext() { 719 if (mSaveAndFinishWorker != null) return; 720 // TODO(b/120484642): This is a point of entry for passwords from the UI 721 final Editable passwordText = mPasswordEntry.getText(); 722 if (TextUtils.isEmpty(passwordText)) { 723 return; 724 } 725 mChosenPassword = mIsAlphaMode ? LockscreenCredential.createPassword(passwordText) 726 : LockscreenCredential.createPin(passwordText); 727 if (mUiStage == Stage.Introduction) { 728 if (validatePassword(mChosenPassword)) { 729 mFirstPassword = mChosenPassword; 730 mPasswordEntry.setText(""); 731 updateStage(Stage.NeedToConfirm); 732 } else { 733 mChosenPassword.zeroize(); 734 } 735 } else if (mUiStage == Stage.NeedToConfirm) { 736 if (mChosenPassword.equals(mFirstPassword)) { 737 startSaveAndFinish(); 738 } else { 739 CharSequence tmp = mPasswordEntry.getText(); 740 if (tmp != null) { 741 Selection.setSelection((Spannable) tmp, 0, tmp.length()); 742 } 743 updateStage(Stage.ConfirmWrong); 744 mChosenPassword.zeroize(); 745 } 746 } 747 } 748 setNextEnabled(boolean enabled)749 protected void setNextEnabled(boolean enabled) { 750 mNextButton.setEnabled(enabled); 751 } 752 setNextText(int text)753 protected void setNextText(int text) { 754 mNextButton.setText(getActivity(), text); 755 } 756 onSkipOrClearButtonClick(View view)757 protected void onSkipOrClearButtonClick(View view) { 758 mPasswordEntry.setText(""); 759 } 760 onNextButtonClick(View view)761 protected void onNextButtonClick(View view) { 762 handleNext(); 763 } 764 onEditorAction(TextView v, int actionId, KeyEvent event)765 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 766 // Check if this was the result of hitting the enter or "done" key 767 if (actionId == EditorInfo.IME_NULL 768 || actionId == EditorInfo.IME_ACTION_DONE 769 || actionId == EditorInfo.IME_ACTION_NEXT) { 770 handleNext(); 771 return true; 772 } 773 return false; 774 } 775 776 /** 777 * @param errorCode error code returned from password validation. 778 * @return an array of messages describing the error, important messages come first. 779 */ convertErrorCodeToMessages()780 String[] convertErrorCodeToMessages() { 781 List<String> messages = new ArrayList<>(); 782 for (PasswordValidationError error : mValidationErrors) { 783 switch (error.errorCode) { 784 case CONTAINS_INVALID_CHARACTERS: 785 messages.add(getString(R.string.lockpassword_illegal_character)); 786 break; 787 case NOT_ENOUGH_UPPER_CASE: 788 messages.add(getResources().getQuantityString( 789 R.plurals.lockpassword_password_requires_uppercase, 790 error.requirement, error.requirement)); 791 break; 792 case NOT_ENOUGH_LOWER_CASE: 793 messages.add(getResources().getQuantityString( 794 R.plurals.lockpassword_password_requires_lowercase, 795 error.requirement, error.requirement)); 796 break; 797 case NOT_ENOUGH_LETTERS: 798 messages.add(getResources().getQuantityString( 799 R.plurals.lockpassword_password_requires_letters, 800 error.requirement, error.requirement)); 801 break; 802 case NOT_ENOUGH_DIGITS: 803 messages.add(getResources().getQuantityString( 804 R.plurals.lockpassword_password_requires_numeric, 805 error.requirement, error.requirement)); 806 break; 807 case NOT_ENOUGH_SYMBOLS: 808 messages.add(getResources().getQuantityString( 809 R.plurals.lockpassword_password_requires_symbols, 810 error.requirement, error.requirement)); 811 break; 812 case NOT_ENOUGH_NON_LETTER: 813 messages.add(getResources().getQuantityString( 814 R.plurals.lockpassword_password_requires_nonletter, 815 error.requirement, error.requirement)); 816 break; 817 case NOT_ENOUGH_NON_DIGITS: 818 messages.add(getResources().getQuantityString( 819 R.plurals.lockpassword_password_requires_nonnumerical, 820 error.requirement, error.requirement)); 821 break; 822 case TOO_SHORT: 823 messages.add(getResources().getQuantityString( 824 mIsAlphaMode 825 ? R.plurals.lockpassword_password_too_short 826 : R.plurals.lockpassword_pin_too_short, 827 error.requirement, error.requirement)); 828 break; 829 case TOO_LONG: 830 messages.add(getResources().getQuantityString( 831 mIsAlphaMode 832 ? R.plurals.lockpassword_password_too_long 833 : R.plurals.lockpassword_pin_too_long, 834 error.requirement + 1, error.requirement + 1)); 835 break; 836 case CONTAINS_SEQUENCE: 837 messages.add(getString(R.string.lockpassword_pin_no_sequential_digits)); 838 break; 839 case RECENTLY_USED: 840 messages.add(getString(mIsAlphaMode 841 ? R.string.lockpassword_password_recently_used 842 : R.string.lockpassword_pin_recently_used)); 843 break; 844 default: 845 Log.wtf(TAG, "unknown error validating password: " + error); 846 } 847 } 848 849 return messages.toArray(new String[0]); 850 } 851 852 /** 853 * Update the hint based on current Stage and length of password entry 854 */ updateUi()855 protected void updateUi() { 856 final boolean canInput = mSaveAndFinishWorker == null; 857 858 LockscreenCredential password = mIsAlphaMode 859 ? LockscreenCredential.createPasswordOrNone(mPasswordEntry.getText()) 860 : LockscreenCredential.createPinOrNone(mPasswordEntry.getText()); 861 final int length = password.size(); 862 if (mUiStage == Stage.Introduction) { 863 mPasswordRestrictionView.setVisibility(View.VISIBLE); 864 final boolean passwordCompliant = validatePassword(password); 865 String[] messages = convertErrorCodeToMessages(); 866 // Update the fulfillment of requirements. 867 mPasswordRequirementAdapter.setRequirements(messages); 868 // Enable/Disable the next button accordingly. 869 setNextEnabled(passwordCompliant); 870 } else { 871 // Hide password requirement view when we are just asking user to confirm the pw. 872 mPasswordRestrictionView.setVisibility(View.GONE); 873 setHeaderText(getString(mUiStage.getHint(mIsAlphaMode, getStageType(), 874 mIsManagedProfile))); 875 setNextEnabled(canInput && length >= LockPatternUtils.MIN_LOCK_PASSWORD_SIZE); 876 mSkipOrClearButton.setVisibility(toVisibility(canInput && length > 0)); 877 } 878 int message = mUiStage.getMessage(mIsAlphaMode, getStageType()); 879 if (message != 0) { 880 mMessage.setVisibility(View.VISIBLE); 881 mMessage.setText(message); 882 } else { 883 mMessage.setVisibility(View.INVISIBLE); 884 } 885 886 setNextText(mUiStage.buttonText); 887 mPasswordEntryInputDisabler.setInputEnabled(canInput); 888 password.zeroize(); 889 } 890 toVisibility(boolean visibleOrGone)891 protected int toVisibility(boolean visibleOrGone) { 892 return visibleOrGone ? View.VISIBLE : View.GONE; 893 } 894 setHeaderText(String text)895 private void setHeaderText(String text) { 896 // Only set the text if it is different than the existing one to avoid announcing again. 897 if (!TextUtils.isEmpty(mLayout.getHeaderText()) 898 && mLayout.getHeaderText().toString().equals(text)) { 899 return; 900 } 901 mLayout.setHeaderText(text); 902 } 903 afterTextChanged(Editable s)904 public void afterTextChanged(Editable s) { 905 // Changing the text while error displayed resets to NeedToConfirm state 906 if (mUiStage == Stage.ConfirmWrong) { 907 mUiStage = Stage.NeedToConfirm; 908 } 909 // Schedule the UI update. 910 mTextChangedHandler.notifyAfterTextChanged(); 911 } 912 beforeTextChanged(CharSequence s, int start, int count, int after)913 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 914 915 } 916 onTextChanged(CharSequence s, int start, int before, int count)917 public void onTextChanged(CharSequence s, int start, int before, int count) { 918 919 } 920 startSaveAndFinish()921 private void startSaveAndFinish() { 922 if (mSaveAndFinishWorker != null) { 923 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker."); 924 return; 925 } 926 927 mPasswordEntryInputDisabler.setInputEnabled(false); 928 setNextEnabled(false); 929 930 mSaveAndFinishWorker = new SaveAndFinishWorker(); 931 mSaveAndFinishWorker.setListener(this); 932 933 getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, 934 FRAGMENT_TAG_SAVE_AND_FINISH).commit(); 935 getFragmentManager().executePendingTransactions(); 936 937 final Intent intent = getActivity().getIntent(); 938 final boolean required = intent.getBooleanExtra( 939 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 940 if (mUnificationProfileId != UserHandle.USER_NULL) { 941 try (LockscreenCredential profileCredential = (LockscreenCredential) 942 intent.getParcelableExtra(EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL)) { 943 mSaveAndFinishWorker.setProfileToUnify(mUnificationProfileId, 944 profileCredential); 945 } 946 } 947 mSaveAndFinishWorker.start(mLockPatternUtils, required, mRequestGatekeeperPassword, 948 mChosenPassword, mCurrentCredential, mUserId); 949 } 950 951 @Override onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)952 public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { 953 getActivity().setResult(RESULT_FINISHED, resultData); 954 955 if (mChosenPassword != null) { 956 mChosenPassword.zeroize(); 957 } 958 if (mCurrentCredential != null) { 959 mCurrentCredential.zeroize(); 960 } 961 if (mFirstPassword != null) { 962 mFirstPassword.zeroize(); 963 } 964 965 mPasswordEntry.setText(""); 966 967 if (!wasSecureBefore) { 968 Intent intent = getRedactionInterstitialIntent(getActivity()); 969 if (intent != null) { 970 startActivity(intent); 971 } 972 } 973 getActivity().finish(); 974 } 975 976 class TextChangedHandler extends Handler { 977 private static final int ON_TEXT_CHANGED = 1; 978 private static final int DELAY_IN_MILLISECOND = 100; 979 980 /** 981 * With the introduction of delay, we batch processing the text changed event to reduce 982 * unnecessary UI updates. 983 */ notifyAfterTextChanged()984 private void notifyAfterTextChanged() { 985 removeMessages(ON_TEXT_CHANGED); 986 sendEmptyMessageDelayed(ON_TEXT_CHANGED, DELAY_IN_MILLISECOND); 987 } 988 989 @Override handleMessage(Message msg)990 public void handleMessage(Message msg) { 991 if (getActivity() == null) { 992 return; 993 } 994 if (msg.what == ON_TEXT_CHANGED) { 995 updateUi(); 996 } 997 } 998 } 999 } 1000 1001 public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase { 1002 1003 private LockscreenCredential mChosenPassword; 1004 private LockscreenCredential mCurrentCredential; 1005 start(LockPatternUtils utils, boolean required, boolean requestGatekeeperPassword, LockscreenCredential chosenPassword, LockscreenCredential currentCredential, int userId)1006 public void start(LockPatternUtils utils, boolean required, 1007 boolean requestGatekeeperPassword, LockscreenCredential chosenPassword, 1008 LockscreenCredential currentCredential, int userId) { 1009 prepare(utils, required, requestGatekeeperPassword, userId); 1010 1011 mChosenPassword = chosenPassword; 1012 mCurrentCredential = currentCredential != null ? currentCredential 1013 : LockscreenCredential.createNone(); 1014 mUserId = userId; 1015 1016 start(); 1017 } 1018 1019 @Override saveAndVerifyInBackground()1020 protected Pair<Boolean, Intent> saveAndVerifyInBackground() { 1021 final boolean success = mUtils.setLockCredential( 1022 mChosenPassword, mCurrentCredential, mUserId); 1023 if (success) { 1024 unifyProfileCredentialIfRequested(); 1025 } 1026 Intent result = null; 1027 if (success && mRequestGatekeeperPassword) { 1028 // If a Gatekeeper Password was requested, invoke the LockSettingsService code 1029 // path to return a Gatekeeper Password based on the credential that the user 1030 // chose. This should only be run if the credential was successfully set. 1031 final VerifyCredentialResponse response = mUtils.verifyCredential(mChosenPassword, 1032 mUserId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE); 1033 1034 if (!response.isMatched() || !response.containsGatekeeperPasswordHandle()) { 1035 Log.e(TAG, "critical: bad response or missing GK PW handle for known good" 1036 + " password: " + response.toString()); 1037 } 1038 1039 result = new Intent(); 1040 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1041 response.getGatekeeperPasswordHandle()); 1042 } 1043 return Pair.create(success, result); 1044 } 1045 } 1046 } 1047