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