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