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