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