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_QUALITY_ALPHABETIC; 20 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; 21 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; 22 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 23 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; 24 25 import android.app.Activity; 26 import android.app.Fragment; 27 import android.app.admin.DevicePolicyManager; 28 import android.app.admin.PasswordMetrics; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.res.Resources.Theme; 32 import android.graphics.Insets; 33 import android.graphics.Typeface; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.Message; 37 import android.support.annotation.StringRes; 38 import android.support.v7.widget.LinearLayoutManager; 39 import android.support.v7.widget.RecyclerView; 40 import android.text.Editable; 41 import android.text.InputType; 42 import android.text.Selection; 43 import android.text.Spannable; 44 import android.text.TextUtils; 45 import android.text.TextWatcher; 46 import android.util.Log; 47 import android.view.KeyEvent; 48 import android.view.LayoutInflater; 49 import android.view.View; 50 import android.view.View.OnClickListener; 51 import android.view.ViewGroup; 52 import android.view.inputmethod.EditorInfo; 53 import android.widget.Button; 54 import android.widget.LinearLayout; 55 import android.widget.TextView; 56 import android.widget.TextView.OnEditorActionListener; 57 58 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 59 import com.android.internal.widget.LockPatternUtils; 60 import com.android.internal.widget.LockPatternUtils.RequestThrottledException; 61 import com.android.internal.widget.TextViewInputDisabler; 62 import com.android.settings.EncryptionInterstitial; 63 import com.android.settings.R; 64 import com.android.settings.SettingsActivity; 65 import com.android.settings.SetupWizardUtils; 66 import com.android.settings.Utils; 67 import com.android.settings.core.InstrumentedFragment; 68 import com.android.settings.notification.RedactionInterstitial; 69 import com.android.settings.widget.ImeAwareEditText; 70 import com.android.setupwizardlib.GlifLayout; 71 72 import java.util.ArrayList; 73 import java.util.List; 74 75 public class ChooseLockPassword extends SettingsActivity { 76 public static final String PASSWORD_MIN_KEY = "lockscreen.password_min"; 77 public static final String PASSWORD_MAX_KEY = "lockscreen.password_max"; 78 public static final String PASSWORD_MIN_LETTERS_KEY = "lockscreen.password_min_letters"; 79 public static final String PASSWORD_MIN_LOWERCASE_KEY = "lockscreen.password_min_lowercase"; 80 public static final String PASSWORD_MIN_UPPERCASE_KEY = "lockscreen.password_min_uppercase"; 81 public static final String PASSWORD_MIN_NUMERIC_KEY = "lockscreen.password_min_numeric"; 82 public static final String PASSWORD_MIN_SYMBOLS_KEY = "lockscreen.password_min_symbols"; 83 public static final String PASSWORD_MIN_NONLETTER_KEY = "lockscreen.password_min_nonletter"; 84 85 private static final String TAG = "ChooseLockPassword"; 86 87 @Override getIntent()88 public Intent getIntent() { 89 Intent modIntent = new Intent(super.getIntent()); 90 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName()); 91 return modIntent; 92 } 93 94 @Override onApplyThemeResource(Theme theme, int resid, boolean first)95 protected void onApplyThemeResource(Theme theme, int resid, boolean first) { 96 resid = SetupWizardUtils.getTheme(getIntent()); 97 super.onApplyThemeResource(theme, resid, first); 98 } 99 100 public static class IntentBuilder { 101 102 private final Intent mIntent; 103 IntentBuilder(Context context)104 public IntentBuilder(Context context) { 105 mIntent = new Intent(context, ChooseLockPassword.class); 106 mIntent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, false); 107 mIntent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, false); 108 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 109 } 110 setPasswordQuality(int quality)111 public IntentBuilder setPasswordQuality(int quality) { 112 mIntent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality); 113 return this; 114 } 115 setPasswordLengthRange(int min, int max)116 public IntentBuilder setPasswordLengthRange(int min, int max) { 117 mIntent.putExtra(PASSWORD_MIN_KEY, min); 118 mIntent.putExtra(PASSWORD_MAX_KEY, max); 119 return this; 120 } 121 setUserId(int userId)122 public IntentBuilder setUserId(int userId) { 123 mIntent.putExtra(Intent.EXTRA_USER_ID, userId); 124 return this; 125 } 126 setChallenge(long challenge)127 public IntentBuilder setChallenge(long challenge) { 128 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true); 129 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge); 130 return this; 131 } 132 setPassword(String password)133 public IntentBuilder setPassword(String password) { 134 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, password); 135 return this; 136 } 137 setForFingerprint(boolean forFingerprint)138 public IntentBuilder setForFingerprint(boolean forFingerprint) { 139 mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, forFingerprint); 140 return this; 141 } 142 build()143 public Intent build() { 144 return mIntent; 145 } 146 } 147 148 @Override isValidFragment(String fragmentName)149 protected boolean isValidFragment(String fragmentName) { 150 if (ChooseLockPasswordFragment.class.getName().equals(fragmentName)) return true; 151 return false; 152 } 153 getFragmentClass()154 /* package */ Class<? extends Fragment> getFragmentClass() { 155 return ChooseLockPasswordFragment.class; 156 } 157 158 @Override onCreate(Bundle savedInstanceState)159 protected void onCreate(Bundle savedInstanceState) { 160 super.onCreate(savedInstanceState); 161 boolean forFingerprint = getIntent() 162 .getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false); 163 CharSequence msg = getText(forFingerprint 164 ? R.string.lockpassword_choose_your_password_header_for_fingerprint 165 : R.string.lockpassword_choose_your_screen_lock_header); 166 setTitle(msg); 167 LinearLayout layout = (LinearLayout) findViewById(R.id.content_parent); 168 layout.setFitsSystemWindows(false); 169 } 170 171 public static class ChooseLockPasswordFragment extends InstrumentedFragment 172 implements OnClickListener, OnEditorActionListener, TextWatcher, 173 SaveAndFinishWorker.Listener { 174 private static final String KEY_FIRST_PIN = "first_pin"; 175 private static final String KEY_UI_STAGE = "ui_stage"; 176 private static final String KEY_CURRENT_PASSWORD = "current_password"; 177 private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker"; 178 179 private String mCurrentPassword; 180 private String mChosenPassword; 181 private boolean mHasChallenge; 182 private long mChallenge; 183 private ImeAwareEditText mPasswordEntry; 184 private TextViewInputDisabler mPasswordEntryInputDisabler; 185 private int mPasswordMinLength = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE; 186 private int mPasswordMaxLength = 16; 187 private int mPasswordMinLetters = 0; 188 private int mPasswordMinUpperCase = 0; 189 private int mPasswordMinLowerCase = 0; 190 private int mPasswordMinSymbols = 0; 191 private int mPasswordMinNumeric = 0; 192 private int mPasswordMinNonLetter = 0; 193 private int mPasswordMinLengthToFulfillAllPolicies = 0; 194 protected int mUserId; 195 private boolean mHideDrawer = false; 196 private byte[] mPasswordHistoryHashFactor; 197 /** 198 * Password requirements that we need to verify. 199 */ 200 private int[] mPasswordRequirements; 201 202 private LockPatternUtils mLockPatternUtils; 203 private SaveAndFinishWorker mSaveAndFinishWorker; 204 private int mRequestedQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 205 private ChooseLockSettingsHelper mChooseLockSettingsHelper; 206 protected Stage mUiStage = Stage.Introduction; 207 private PasswordRequirementAdapter mPasswordRequirementAdapter; 208 private GlifLayout mLayout; 209 protected boolean mForFingerprint; 210 211 private String mFirstPin; 212 private RecyclerView mPasswordRestrictionView; 213 protected boolean mIsAlphaMode; 214 protected Button mSkipButton; 215 private Button mClearButton; 216 private Button mNextButton; 217 private TextView mMessage; 218 219 private TextChangedHandler mTextChangedHandler; 220 221 private static final int CONFIRM_EXISTING_REQUEST = 58; 222 static final int RESULT_FINISHED = RESULT_FIRST_USER; 223 224 private static final int MIN_LETTER_IN_PASSWORD = 0; 225 private static final int MIN_UPPER_LETTERS_IN_PASSWORD = 1; 226 private static final int MIN_LOWER_LETTERS_IN_PASSWORD = 2; 227 private static final int MIN_SYMBOLS_IN_PASSWORD = 3; 228 private static final int MIN_NUMBER_IN_PASSWORD = 4; 229 private static final int MIN_NON_LETTER_IN_PASSWORD = 5; 230 231 // Error code returned from {@link #validatePassword(String)}. 232 static final int NO_ERROR = 0; 233 static final int CONTAIN_INVALID_CHARACTERS = 1 << 0; 234 static final int TOO_SHORT = 1 << 1; 235 static final int TOO_LONG = 1 << 2; 236 static final int CONTAIN_NON_DIGITS = 1 << 3; 237 static final int CONTAIN_SEQUENTIAL_DIGITS = 1 << 4; 238 static final int RECENTLY_USED = 1 << 5; 239 static final int NOT_ENOUGH_LETTER = 1 << 6; 240 static final int NOT_ENOUGH_UPPER_CASE = 1 << 7; 241 static final int NOT_ENOUGH_LOWER_CASE = 1 << 8; 242 static final int NOT_ENOUGH_DIGITS = 1 << 9; 243 static final int NOT_ENOUGH_SYMBOLS = 1 << 10; 244 static final int NOT_ENOUGH_NON_LETTER = 1 << 11; 245 246 /** 247 * Keep track internally of where the user is in choosing a pattern. 248 */ 249 protected enum Stage { 250 251 Introduction( 252 R.string.lockpassword_choose_your_screen_lock_header, 253 R.string.lockpassword_choose_your_password_header_for_fingerprint, 254 R.string.lockpassword_choose_your_screen_lock_header, 255 R.string.lockpassword_choose_your_pin_header_for_fingerprint, 256 R.string.lockpassword_choose_your_password_message, 257 R.string.lock_settings_picker_fingerprint_added_security_message, 258 R.string.lockpassword_choose_your_pin_message, 259 R.string.lock_settings_picker_fingerprint_added_security_message, 260 R.string.next_label), 261 262 NeedToConfirm( 263 R.string.lockpassword_confirm_your_password_header, 264 R.string.lockpassword_confirm_your_password_header, 265 R.string.lockpassword_confirm_your_pin_header, 266 R.string.lockpassword_confirm_your_pin_header, 267 0, 268 0, 269 0, 270 0, 271 R.string.lockpassword_confirm_label), 272 273 ConfirmWrong( 274 R.string.lockpassword_confirm_passwords_dont_match, 275 R.string.lockpassword_confirm_passwords_dont_match, 276 R.string.lockpassword_confirm_pins_dont_match, 277 R.string.lockpassword_confirm_pins_dont_match, 278 0, 279 0, 280 0, 281 0, 282 R.string.lockpassword_confirm_label); 283 Stage(int hintInAlpha, int hintInAlphaForFingerprint, int hintInNumeric, int hintInNumericForFingerprint, int messageInAlpha, int messageInAlphaForFingerprint, int messageInNumeric, int messageInNumericForFingerprint, int nextButtonText)284 Stage(int hintInAlpha, int hintInAlphaForFingerprint, 285 int hintInNumeric, int hintInNumericForFingerprint, 286 int messageInAlpha, int messageInAlphaForFingerprint, 287 int messageInNumeric, int messageInNumericForFingerprint, 288 int nextButtonText) { 289 this.alphaHint = hintInAlpha; 290 this.alphaHintForFingerprint = hintInAlphaForFingerprint; 291 this.numericHint = hintInNumeric; 292 this.numericHintForFingerprint = hintInNumericForFingerprint; 293 this.alphaMessage = messageInAlpha; 294 this.alphaMessageForFingerprint = messageInAlphaForFingerprint; 295 this.numericMessage = messageInNumeric; 296 this.numericMessageForFingerprint = messageInNumericForFingerprint; 297 this.buttonText = nextButtonText; 298 } 299 300 public final int alphaHint; 301 public final int alphaHintForFingerprint; 302 public final int numericHint; 303 public final int numericHintForFingerprint; 304 public final int alphaMessage; 305 public final int alphaMessageForFingerprint; 306 public final int numericMessage; 307 public final int numericMessageForFingerprint; 308 public final int buttonText; 309 getHint(boolean isAlpha, boolean isFingerprint)310 public @StringRes int getHint(boolean isAlpha, boolean isFingerprint) { 311 if (isAlpha) { 312 return isFingerprint ? alphaHintForFingerprint : alphaHint; 313 } else { 314 return isFingerprint ? numericHintForFingerprint : numericHint; 315 } 316 } 317 getMessage(boolean isAlpha, boolean isFingerprint)318 public @StringRes int getMessage(boolean isAlpha, boolean isFingerprint) { 319 if (isAlpha) { 320 return isFingerprint ? alphaMessageForFingerprint : alphaMessage; 321 } else { 322 return isFingerprint ? numericMessageForFingerprint : numericMessage; 323 } 324 } 325 } 326 327 // required constructor for fragments ChooseLockPasswordFragment()328 public ChooseLockPasswordFragment() { 329 330 } 331 332 @Override onCreate(Bundle savedInstanceState)333 public void onCreate(Bundle savedInstanceState) { 334 super.onCreate(savedInstanceState); 335 mLockPatternUtils = new LockPatternUtils(getActivity()); 336 Intent intent = getActivity().getIntent(); 337 if (!(getActivity() instanceof ChooseLockPassword)) { 338 throw new SecurityException("Fragment contained in wrong activity"); 339 } 340 // Only take this argument into account if it belongs to the current profile. 341 mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras()); 342 mForFingerprint = intent.getBooleanExtra( 343 ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false); 344 processPasswordRequirements(intent); 345 mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity()); 346 mHideDrawer = getActivity().getIntent().getBooleanExtra(EXTRA_HIDE_DRAWER, false); 347 348 if (intent.getBooleanExtra( 349 ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) { 350 SaveAndFinishWorker w = new SaveAndFinishWorker(); 351 final boolean required = getActivity().getIntent().getBooleanExtra( 352 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 353 String current = intent.getStringExtra( 354 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 355 w.setBlocking(true); 356 w.setListener(this); 357 w.start(mChooseLockSettingsHelper.utils(), required, 358 false, 0, current, current, mRequestedQuality, mUserId); 359 } 360 mTextChangedHandler = new TextChangedHandler(); 361 } 362 363 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)364 public View onCreateView(LayoutInflater inflater, ViewGroup container, 365 Bundle savedInstanceState) { 366 return inflater.inflate(R.layout.choose_lock_password, container, false); 367 } 368 369 @Override onViewCreated(View view, Bundle savedInstanceState)370 public void onViewCreated(View view, Bundle savedInstanceState) { 371 super.onViewCreated(view, savedInstanceState); 372 373 mLayout = (GlifLayout) view; 374 375 // Make the password container consume the optical insets so the edit text is aligned 376 // with the sides of the parent visually. 377 ViewGroup container = view.findViewById(R.id.password_container); 378 container.setOpticalInsets(Insets.NONE); 379 380 mSkipButton = (Button) view.findViewById(R.id.skip_button); 381 mSkipButton.setOnClickListener(this); 382 mNextButton = (Button) view.findViewById(R.id.next_button); 383 mNextButton.setOnClickListener(this); 384 mClearButton = view.findViewById(R.id.clear_button); 385 mClearButton.setOnClickListener(this); 386 387 388 mMessage = view.findViewById(R.id.message); 389 if (mForFingerprint) { 390 mLayout.setIcon(getActivity().getDrawable(R.drawable.ic_fingerprint_header)); 391 } 392 393 mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == mRequestedQuality 394 || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == mRequestedQuality 395 || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == mRequestedQuality; 396 397 setupPasswordRequirementsView(view); 398 399 mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); 400 mPasswordEntry = view.findViewById(R.id.password_entry); 401 mPasswordEntry.setOnEditorActionListener(this); 402 mPasswordEntry.addTextChangedListener(this); 403 mPasswordEntry.requestFocus(); 404 mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry); 405 406 final Activity activity = getActivity(); 407 408 int currentType = mPasswordEntry.getInputType(); 409 mPasswordEntry.setInputType(mIsAlphaMode ? currentType 410 : (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)); 411 // Can't set via XML since setInputType resets the fontFamily to null 412 mPasswordEntry.setTypeface(Typeface.create( 413 getContext().getString(com.android.internal.R.string.config_headlineFontFamily), 414 Typeface.NORMAL)); 415 416 Intent intent = getActivity().getIntent(); 417 final boolean confirmCredentials = intent.getBooleanExtra( 418 ChooseLockGeneric.CONFIRM_CREDENTIALS, true); 419 mCurrentPassword = intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 420 mHasChallenge = intent.getBooleanExtra( 421 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 422 mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0); 423 if (savedInstanceState == null) { 424 updateStage(Stage.Introduction); 425 if (confirmCredentials) { 426 mChooseLockSettingsHelper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, 427 getString(R.string.unlock_set_unlock_launch_picker_title), true, 428 mUserId); 429 } 430 } else { 431 // restore from previous state 432 mFirstPin = savedInstanceState.getString(KEY_FIRST_PIN); 433 final String state = savedInstanceState.getString(KEY_UI_STAGE); 434 if (state != null) { 435 mUiStage = Stage.valueOf(state); 436 updateStage(mUiStage); 437 } 438 439 if (mCurrentPassword == null) { 440 mCurrentPassword = savedInstanceState.getString(KEY_CURRENT_PASSWORD); 441 } 442 443 // Re-attach to the exiting worker if there is one. 444 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag( 445 FRAGMENT_TAG_SAVE_AND_FINISH); 446 } 447 448 if (activity instanceof SettingsActivity) { 449 final SettingsActivity sa = (SettingsActivity) activity; 450 int title = Stage.Introduction.getHint(mIsAlphaMode, mForFingerprint); 451 sa.setTitle(title); 452 mLayout.setHeaderText(title); 453 } 454 } 455 setupPasswordRequirementsView(View view)456 private void setupPasswordRequirementsView(View view) { 457 // Construct passwordRequirements and requirementDescriptions. 458 List<Integer> passwordRequirements = new ArrayList<>(); 459 List<String> requirementDescriptions = new ArrayList<>(); 460 if (mPasswordMinUpperCase > 0) { 461 passwordRequirements.add(MIN_UPPER_LETTERS_IN_PASSWORD); 462 requirementDescriptions.add(getResources().getQuantityString( 463 R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase, 464 mPasswordMinUpperCase)); 465 } 466 if (mPasswordMinLowerCase > 0) { 467 passwordRequirements.add(MIN_LOWER_LETTERS_IN_PASSWORD); 468 requirementDescriptions.add(getResources().getQuantityString( 469 R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase, 470 mPasswordMinLowerCase)); 471 } 472 if (mPasswordMinLetters > 0) { 473 if (mPasswordMinLetters > mPasswordMinUpperCase + mPasswordMinLowerCase) { 474 passwordRequirements.add(MIN_LETTER_IN_PASSWORD); 475 requirementDescriptions.add(getResources().getQuantityString( 476 R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters, 477 mPasswordMinLetters)); 478 } 479 } 480 if (mPasswordMinNumeric > 0) { 481 passwordRequirements.add(MIN_NUMBER_IN_PASSWORD); 482 requirementDescriptions.add(getResources().getQuantityString( 483 R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric, 484 mPasswordMinNumeric)); 485 } 486 if (mPasswordMinSymbols > 0) { 487 passwordRequirements.add(MIN_SYMBOLS_IN_PASSWORD); 488 requirementDescriptions.add(getResources().getQuantityString( 489 R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols, 490 mPasswordMinSymbols)); 491 } 492 if (mPasswordMinNonLetter > 0) { 493 if (mPasswordMinNonLetter > mPasswordMinNumeric + mPasswordMinSymbols) { 494 passwordRequirements.add(MIN_NON_LETTER_IN_PASSWORD); 495 requirementDescriptions.add(getResources().getQuantityString( 496 R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter, 497 498 mPasswordMinNonLetter)); 499 } 500 } 501 // Convert list to array. 502 mPasswordRequirements = passwordRequirements.stream().mapToInt(i -> i).toArray(); 503 mPasswordRestrictionView = 504 (RecyclerView) view.findViewById(R.id.password_requirements_view); 505 mPasswordRestrictionView.setLayoutManager(new LinearLayoutManager(getActivity())); 506 mPasswordRequirementAdapter = new PasswordRequirementAdapter(); 507 mPasswordRestrictionView.setAdapter(mPasswordRequirementAdapter); 508 } 509 510 @Override getMetricsCategory()511 public int getMetricsCategory() { 512 return MetricsEvent.CHOOSE_LOCK_PASSWORD; 513 } 514 515 @Override onResume()516 public void onResume() { 517 super.onResume(); 518 updateStage(mUiStage); 519 if (mSaveAndFinishWorker != null) { 520 mSaveAndFinishWorker.setListener(this); 521 } else { 522 mPasswordEntry.requestFocus(); 523 mPasswordEntry.scheduleShowSoftInput(); 524 } 525 } 526 527 @Override onPause()528 public void onPause() { 529 if (mSaveAndFinishWorker != null) { 530 mSaveAndFinishWorker.setListener(null); 531 } 532 super.onPause(); 533 } 534 535 @Override onSaveInstanceState(Bundle outState)536 public void onSaveInstanceState(Bundle outState) { 537 super.onSaveInstanceState(outState); 538 outState.putString(KEY_UI_STAGE, mUiStage.name()); 539 outState.putString(KEY_FIRST_PIN, mFirstPin); 540 outState.putString(KEY_CURRENT_PASSWORD, mCurrentPassword); 541 } 542 543 @Override onActivityResult(int requestCode, int resultCode, Intent data)544 public void onActivityResult(int requestCode, int resultCode, 545 Intent data) { 546 super.onActivityResult(requestCode, resultCode, data); 547 switch (requestCode) { 548 case CONFIRM_EXISTING_REQUEST: 549 if (resultCode != Activity.RESULT_OK) { 550 getActivity().setResult(RESULT_FINISHED); 551 getActivity().finish(); 552 } else { 553 mCurrentPassword = data.getStringExtra( 554 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 555 } 556 break; 557 } 558 } 559 getRedactionInterstitialIntent(Context context)560 protected Intent getRedactionInterstitialIntent(Context context) { 561 return RedactionInterstitial.createStartIntent(context, mUserId); 562 } 563 updateStage(Stage stage)564 protected void updateStage(Stage stage) { 565 final Stage previousStage = mUiStage; 566 mUiStage = stage; 567 updateUi(); 568 569 // If the stage changed, announce the header for accessibility. This 570 // is a no-op when accessibility is disabled. 571 if (previousStage != stage) { 572 mLayout.announceForAccessibility(mLayout.getHeaderText()); 573 } 574 } 575 576 /** 577 * Read the requirements from {@link DevicePolicyManager} and intent and aggregate them. 578 * 579 * @param intent the incoming intent 580 */ processPasswordRequirements(Intent intent)581 private void processPasswordRequirements(Intent intent) { 582 final int dpmPasswordQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId); 583 mRequestedQuality = Math.max(intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, 584 mRequestedQuality), dpmPasswordQuality); 585 mPasswordMinLength = Math.max(Math.max( 586 LockPatternUtils.MIN_LOCK_PASSWORD_SIZE, 587 intent.getIntExtra(PASSWORD_MIN_KEY, mPasswordMinLength)), 588 mLockPatternUtils.getRequestedMinimumPasswordLength(mUserId)); 589 mPasswordMaxLength = intent.getIntExtra(PASSWORD_MAX_KEY, mPasswordMaxLength); 590 mPasswordMinLetters = Math.max(intent.getIntExtra(PASSWORD_MIN_LETTERS_KEY, 591 mPasswordMinLetters), mLockPatternUtils.getRequestedPasswordMinimumLetters( 592 mUserId)); 593 mPasswordMinUpperCase = Math.max(intent.getIntExtra(PASSWORD_MIN_UPPERCASE_KEY, 594 mPasswordMinUpperCase), mLockPatternUtils.getRequestedPasswordMinimumUpperCase( 595 mUserId)); 596 mPasswordMinLowerCase = Math.max(intent.getIntExtra(PASSWORD_MIN_LOWERCASE_KEY, 597 mPasswordMinLowerCase), mLockPatternUtils.getRequestedPasswordMinimumLowerCase( 598 mUserId)); 599 mPasswordMinNumeric = Math.max(intent.getIntExtra(PASSWORD_MIN_NUMERIC_KEY, 600 mPasswordMinNumeric), mLockPatternUtils.getRequestedPasswordMinimumNumeric( 601 mUserId)); 602 mPasswordMinSymbols = Math.max(intent.getIntExtra(PASSWORD_MIN_SYMBOLS_KEY, 603 mPasswordMinSymbols), mLockPatternUtils.getRequestedPasswordMinimumSymbols( 604 mUserId)); 605 mPasswordMinNonLetter = Math.max(intent.getIntExtra(PASSWORD_MIN_NONLETTER_KEY, 606 mPasswordMinNonLetter), mLockPatternUtils.getRequestedPasswordMinimumNonLetter( 607 mUserId)); 608 609 // Modify the value based on dpm policy. 610 switch (dpmPasswordQuality) { 611 case PASSWORD_QUALITY_ALPHABETIC: 612 if (mPasswordMinLetters == 0) { 613 mPasswordMinLetters = 1; 614 } 615 break; 616 case PASSWORD_QUALITY_ALPHANUMERIC: 617 if (mPasswordMinLetters == 0) { 618 mPasswordMinLetters = 1; 619 } 620 if (mPasswordMinNumeric == 0) { 621 mPasswordMinNumeric = 1; 622 } 623 break; 624 case PASSWORD_QUALITY_COMPLEX: 625 // Reserve all the requirements. 626 break; 627 default: 628 mPasswordMinNumeric = 0; 629 mPasswordMinLetters = 0; 630 mPasswordMinUpperCase = 0; 631 mPasswordMinLowerCase = 0; 632 mPasswordMinSymbols = 0; 633 mPasswordMinNonLetter = 0; 634 } 635 mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies(); 636 } 637 638 /** 639 * Validates PIN/Password and returns the validation result. 640 * 641 * @param password the raw password the user typed in 642 * @return the validation result. 643 */ validatePassword(String password)644 private int validatePassword(String password) { 645 int errorCode = NO_ERROR; 646 final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password); 647 648 649 if (password.length() < mPasswordMinLength) { 650 if (mPasswordMinLength > mPasswordMinLengthToFulfillAllPolicies) { 651 errorCode |= TOO_SHORT; 652 } 653 } else if (password.length() > mPasswordMaxLength) { 654 errorCode |= TOO_LONG; 655 } else { 656 // The length requirements are fulfilled. 657 final int dpmQuality = mLockPatternUtils.getRequestedPasswordQuality(mUserId); 658 if (dpmQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX && 659 metrics.numeric == password.length()) { 660 // Check for repeated characters or sequences (e.g. '1234', '0000', '2468') 661 // if DevicePolicyManager requires a complex numeric password. There can be 662 // two cases in the UI: 1. User chooses to enroll a PIN, 2. User chooses to 663 // enroll a password but enters a numeric-only pin. We should carry out the 664 // sequence check in both cases. 665 final int sequence = PasswordMetrics.maxLengthSequence(password); 666 if (sequence > PasswordMetrics.MAX_ALLOWED_SEQUENCE) { 667 errorCode |= CONTAIN_SEQUENTIAL_DIGITS; 668 } 669 } 670 // Is the password recently used? 671 if (mLockPatternUtils.checkPasswordHistory(password, getPasswordHistoryHashFactor(), 672 mUserId)) { 673 errorCode |= RECENTLY_USED; 674 } 675 } 676 677 // Allow non-control Latin-1 characters only. 678 for (int i = 0; i < password.length(); i++) { 679 char c = password.charAt(i); 680 if (c < 32 || c > 127) { 681 errorCode |= CONTAIN_INVALID_CHARACTERS; 682 break; 683 } 684 } 685 686 // Ensure no non-digits if we are requesting numbers. This shouldn't be possible unless 687 // user finds some way to bring up soft keyboard. 688 if (mRequestedQuality == PASSWORD_QUALITY_NUMERIC 689 || mRequestedQuality == PASSWORD_QUALITY_NUMERIC_COMPLEX) { 690 if (metrics.letters > 0 || metrics.symbols > 0) { 691 errorCode |= CONTAIN_NON_DIGITS; 692 } 693 } 694 695 // Check the requirements one by one. 696 for (int i = 0; i < mPasswordRequirements.length; i++) { 697 int passwordRestriction = mPasswordRequirements[i]; 698 switch (passwordRestriction) { 699 case MIN_LETTER_IN_PASSWORD: 700 if (metrics.letters < mPasswordMinLetters) { 701 errorCode |= NOT_ENOUGH_LETTER; 702 } 703 break; 704 case MIN_UPPER_LETTERS_IN_PASSWORD: 705 if (metrics.upperCase < mPasswordMinUpperCase) { 706 errorCode |= NOT_ENOUGH_UPPER_CASE; 707 } 708 break; 709 case MIN_LOWER_LETTERS_IN_PASSWORD: 710 if (metrics.lowerCase < mPasswordMinLowerCase) { 711 errorCode |= NOT_ENOUGH_LOWER_CASE; 712 } 713 break; 714 case MIN_SYMBOLS_IN_PASSWORD: 715 if (metrics.symbols < mPasswordMinSymbols) { 716 errorCode |= NOT_ENOUGH_SYMBOLS; 717 } 718 break; 719 case MIN_NUMBER_IN_PASSWORD: 720 if (metrics.numeric < mPasswordMinNumeric) { 721 errorCode |= NOT_ENOUGH_DIGITS; 722 } 723 break; 724 case MIN_NON_LETTER_IN_PASSWORD: 725 if (metrics.nonLetter < mPasswordMinNonLetter) { 726 errorCode |= NOT_ENOUGH_NON_LETTER; 727 } 728 break; 729 } 730 } 731 732 return errorCode; 733 } 734 735 /** 736 * Lazily compute and return the history hash factor of the current user (mUserId), used for 737 * password history check. 738 */ getPasswordHistoryHashFactor()739 private byte[] getPasswordHistoryHashFactor() { 740 if (mPasswordHistoryHashFactor == null) { 741 mPasswordHistoryHashFactor = mLockPatternUtils.getPasswordHistoryHashFactor( 742 mCurrentPassword, mUserId); 743 } 744 return mPasswordHistoryHashFactor; 745 } 746 handleNext()747 public void handleNext() { 748 if (mSaveAndFinishWorker != null) return; 749 mChosenPassword = mPasswordEntry.getText().toString(); 750 if (TextUtils.isEmpty(mChosenPassword)) { 751 return; 752 } 753 if (mUiStage == Stage.Introduction) { 754 if (validatePassword(mChosenPassword) == NO_ERROR) { 755 mFirstPin = mChosenPassword; 756 mPasswordEntry.setText(""); 757 updateStage(Stage.NeedToConfirm); 758 } 759 } else if (mUiStage == Stage.NeedToConfirm) { 760 if (mFirstPin.equals(mChosenPassword)) { 761 startSaveAndFinish(); 762 } else { 763 CharSequence tmp = mPasswordEntry.getText(); 764 if (tmp != null) { 765 Selection.setSelection((Spannable) tmp, 0, tmp.length()); 766 } 767 updateStage(Stage.ConfirmWrong); 768 } 769 } 770 } 771 setNextEnabled(boolean enabled)772 protected void setNextEnabled(boolean enabled) { 773 mNextButton.setEnabled(enabled); 774 } 775 setNextText(int text)776 protected void setNextText(int text) { 777 mNextButton.setText(text); 778 } 779 onClick(View v)780 public void onClick(View v) { 781 switch (v.getId()) { 782 case R.id.next_button: 783 handleNext(); 784 break; 785 786 case R.id.clear_button: 787 mPasswordEntry.setText(""); 788 break; 789 } 790 } 791 onEditorAction(TextView v, int actionId, KeyEvent event)792 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 793 // Check if this was the result of hitting the enter or "done" key 794 if (actionId == EditorInfo.IME_NULL 795 || actionId == EditorInfo.IME_ACTION_DONE 796 || actionId == EditorInfo.IME_ACTION_NEXT) { 797 handleNext(); 798 return true; 799 } 800 return false; 801 } 802 803 /** 804 * @param errorCode error code returned from {@link #validatePassword(String)}. 805 * @return an array of messages describing the error, important messages come first. 806 */ convertErrorCodeToMessages(int errorCode)807 String[] convertErrorCodeToMessages(int errorCode) { 808 List<String> messages = new ArrayList<>(); 809 if ((errorCode & CONTAIN_INVALID_CHARACTERS) > 0) { 810 messages.add(getString(R.string.lockpassword_illegal_character)); 811 } 812 if ((errorCode & CONTAIN_NON_DIGITS) > 0) { 813 messages.add(getString(R.string.lockpassword_pin_contains_non_digits)); 814 } 815 if ((errorCode & NOT_ENOUGH_UPPER_CASE) > 0) { 816 messages.add(getResources().getQuantityString( 817 R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase, 818 mPasswordMinUpperCase)); 819 } 820 if ((errorCode & NOT_ENOUGH_LOWER_CASE) > 0) { 821 messages.add(getResources().getQuantityString( 822 R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase, 823 mPasswordMinLowerCase)); 824 } 825 if ((errorCode & NOT_ENOUGH_LETTER) > 0) { 826 messages.add(getResources().getQuantityString( 827 R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters, 828 mPasswordMinLetters)); 829 } 830 if ((errorCode & NOT_ENOUGH_DIGITS) > 0) { 831 messages.add(getResources().getQuantityString( 832 R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric, 833 mPasswordMinNumeric)); 834 } 835 if ((errorCode & NOT_ENOUGH_SYMBOLS) > 0) { 836 messages.add(getResources().getQuantityString( 837 R.plurals.lockpassword_password_requires_symbols, mPasswordMinSymbols, 838 mPasswordMinSymbols)); 839 } 840 if ((errorCode & NOT_ENOUGH_NON_LETTER) > 0) { 841 messages.add(getResources().getQuantityString( 842 R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter, 843 mPasswordMinNonLetter)); 844 } 845 if ((errorCode & TOO_SHORT) > 0) { 846 messages.add(getString(mIsAlphaMode ? 847 R.string.lockpassword_password_too_short 848 : R.string.lockpassword_pin_too_short, mPasswordMinLength)); 849 } 850 if ((errorCode & TOO_LONG) > 0) { 851 messages.add(getString(mIsAlphaMode ? 852 R.string.lockpassword_password_too_long 853 : R.string.lockpassword_pin_too_long, mPasswordMaxLength + 1)); 854 } 855 if ((errorCode & CONTAIN_SEQUENTIAL_DIGITS) > 0) { 856 messages.add(getString(R.string.lockpassword_pin_no_sequential_digits)); 857 } 858 if ((errorCode & RECENTLY_USED) > 0) { 859 messages.add(getString((mIsAlphaMode) ? R.string.lockpassword_password_recently_used 860 : R.string.lockpassword_pin_recently_used)); 861 } 862 return messages.toArray(new String[0]); 863 } 864 getMinLengthToFulfillAllPolicies()865 private int getMinLengthToFulfillAllPolicies() { 866 final int minLengthForLetters = Math.max(mPasswordMinLetters, 867 mPasswordMinUpperCase + mPasswordMinLowerCase); 868 final int minLengthForNonLetters = Math.max(mPasswordMinNonLetter, 869 mPasswordMinSymbols + mPasswordMinNumeric); 870 return minLengthForLetters + minLengthForNonLetters; 871 } 872 873 /** 874 * Update the hint based on current Stage and length of password entry 875 */ updateUi()876 protected void updateUi() { 877 final boolean canInput = mSaveAndFinishWorker == null; 878 String password = mPasswordEntry.getText().toString(); 879 final int length = password.length(); 880 if (mUiStage == Stage.Introduction) { 881 mPasswordRestrictionView.setVisibility(View.VISIBLE); 882 final int errorCode = validatePassword(password); 883 String[] messages = convertErrorCodeToMessages(errorCode); 884 // Update the fulfillment of requirements. 885 mPasswordRequirementAdapter.setRequirements(messages); 886 // Enable/Disable the next button accordingly. 887 setNextEnabled(errorCode == NO_ERROR); 888 } else { 889 // Hide password requirement view when we are just asking user to confirm the pw. 890 mPasswordRestrictionView.setVisibility(View.GONE); 891 setHeaderText(getString(mUiStage.getHint(mIsAlphaMode, mForFingerprint))); 892 setNextEnabled(canInput && length >= mPasswordMinLength); 893 mClearButton.setEnabled(canInput && length > 0); 894 } 895 int message = mUiStage.getMessage(mIsAlphaMode, mForFingerprint); 896 if (message != 0) { 897 mMessage.setVisibility(View.VISIBLE); 898 mMessage.setText(message); 899 } else { 900 mMessage.setVisibility(View.INVISIBLE); 901 } 902 903 mClearButton.setVisibility(toVisibility(mUiStage != Stage.Introduction)); 904 905 setNextText(mUiStage.buttonText); 906 mPasswordEntryInputDisabler.setInputEnabled(canInput); 907 } 908 toVisibility(boolean visibleOrGone)909 private int toVisibility(boolean visibleOrGone) { 910 return visibleOrGone ? View.VISIBLE : View.GONE; 911 } 912 setHeaderText(String text)913 private void setHeaderText(String text) { 914 // Only set the text if it is different than the existing one to avoid announcing again. 915 if (!TextUtils.isEmpty(mLayout.getHeaderText()) 916 && mLayout.getHeaderText().toString().equals(text)) { 917 return; 918 } 919 mLayout.setHeaderText(text); 920 } 921 afterTextChanged(Editable s)922 public void afterTextChanged(Editable s) { 923 // Changing the text while error displayed resets to NeedToConfirm state 924 if (mUiStage == Stage.ConfirmWrong) { 925 mUiStage = Stage.NeedToConfirm; 926 } 927 // Schedule the UI update. 928 mTextChangedHandler.notifyAfterTextChanged(); 929 } 930 beforeTextChanged(CharSequence s, int start, int count, int after)931 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 932 933 } 934 onTextChanged(CharSequence s, int start, int before, int count)935 public void onTextChanged(CharSequence s, int start, int before, int count) { 936 937 } 938 startSaveAndFinish()939 private void startSaveAndFinish() { 940 if (mSaveAndFinishWorker != null) { 941 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker."); 942 return; 943 } 944 945 mPasswordEntryInputDisabler.setInputEnabled(false); 946 setNextEnabled(false); 947 948 mSaveAndFinishWorker = new SaveAndFinishWorker(); 949 mSaveAndFinishWorker.setListener(this); 950 951 getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, 952 FRAGMENT_TAG_SAVE_AND_FINISH).commit(); 953 getFragmentManager().executePendingTransactions(); 954 955 final boolean required = getActivity().getIntent().getBooleanExtra( 956 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 957 mSaveAndFinishWorker.start(mLockPatternUtils, required, mHasChallenge, mChallenge, 958 mChosenPassword, mCurrentPassword, mRequestedQuality, mUserId); 959 } 960 961 @Override onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)962 public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { 963 getActivity().setResult(RESULT_FINISHED, resultData); 964 965 if (!wasSecureBefore) { 966 Intent intent = getRedactionInterstitialIntent(getActivity()); 967 if (intent != null) { 968 intent.putExtra(EXTRA_HIDE_DRAWER, mHideDrawer); 969 startActivity(intent); 970 } 971 } 972 getActivity().finish(); 973 } 974 975 class TextChangedHandler extends Handler { 976 private static final int ON_TEXT_CHANGED = 1; 977 private static final int DELAY_IN_MILLISECOND = 100; 978 979 /** 980 * With the introduction of delay, we batch processing the text changed event to reduce 981 * unnecessary UI updates. 982 */ notifyAfterTextChanged()983 private void notifyAfterTextChanged() { 984 removeMessages(ON_TEXT_CHANGED); 985 sendEmptyMessageDelayed(ON_TEXT_CHANGED, DELAY_IN_MILLISECOND); 986 } 987 988 @Override handleMessage(Message msg)989 public void handleMessage(Message msg) { 990 if (getActivity() == null) { 991 return; 992 } 993 if (msg.what == ON_TEXT_CHANGED) { 994 updateUi(); 995 } 996 } 997 } 998 } 999 1000 public static class SaveAndFinishWorker extends SaveChosenLockWorkerBase { 1001 1002 private String mChosenPassword; 1003 private String mCurrentPassword; 1004 private int mRequestedQuality; 1005 start(LockPatternUtils utils, boolean required, boolean hasChallenge, long challenge, String chosenPassword, String currentPassword, int requestedQuality, int userId)1006 public void start(LockPatternUtils utils, boolean required, 1007 boolean hasChallenge, long challenge, 1008 String chosenPassword, String currentPassword, int requestedQuality, int userId) { 1009 prepare(utils, required, hasChallenge, challenge, userId); 1010 1011 mChosenPassword = chosenPassword; 1012 mCurrentPassword = currentPassword; 1013 mRequestedQuality = requestedQuality; 1014 mUserId = userId; 1015 1016 start(); 1017 } 1018 1019 @Override saveAndVerifyInBackground()1020 protected Intent saveAndVerifyInBackground() { 1021 Intent result = null; 1022 mUtils.saveLockPassword(mChosenPassword, mCurrentPassword, mRequestedQuality, 1023 mUserId); 1024 1025 if (mHasChallenge) { 1026 byte[] token; 1027 try { 1028 token = mUtils.verifyPassword(mChosenPassword, mChallenge, mUserId); 1029 } catch (RequestThrottledException e) { 1030 token = null; 1031 } 1032 1033 if (token == null) { 1034 Log.e(TAG, "critical: no token returned for known good password."); 1035 } 1036 1037 result = new Intent(); 1038 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); 1039 } 1040 1041 return result; 1042 } 1043 } 1044 } 1045