LockPatternUtils.java revision 3553c296c0e0951a150f1783b2d0ff0d4bfe06cd
1/* 2 * Copyright (C) 2007 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 17package com.android.internal.widget; 18 19import com.android.internal.R; 20import com.android.internal.telephony.ITelephony; 21import com.google.android.collect.Lists; 22 23import android.app.admin.DevicePolicyManager; 24import android.content.ContentResolver; 25import android.content.Context; 26import android.content.Intent; 27import android.os.FileObserver; 28import android.os.IBinder; 29import android.os.RemoteException; 30import android.os.ServiceManager; 31import android.os.SystemClock; 32import android.os.storage.IMountService; 33import android.provider.Settings; 34import android.security.KeyStore; 35import android.telephony.TelephonyManager; 36import android.text.TextUtils; 37import android.util.Log; 38import android.view.View; 39import android.widget.Button; 40import android.widget.TextView; 41 42import java.io.File; 43import java.io.FileNotFoundException; 44import java.io.IOException; 45import java.io.RandomAccessFile; 46import java.security.MessageDigest; 47import java.security.NoSuchAlgorithmException; 48import java.security.SecureRandom; 49import java.util.Arrays; 50import java.util.List; 51import java.util.concurrent.atomic.AtomicBoolean; 52 53/** 54 * Utilities for the lock pattern and its settings. 55 */ 56public class LockPatternUtils { 57 58 private static final String TAG = "LockPatternUtils"; 59 60 private static final String SYSTEM_DIRECTORY = "/system/"; 61 private static final String LOCK_PATTERN_FILE = "gesture.key"; 62 private static final String LOCK_PASSWORD_FILE = "password.key"; 63 64 /** 65 * The maximum number of incorrect attempts before the user is prevented 66 * from trying again for {@link #FAILED_ATTEMPT_TIMEOUT_MS}. 67 */ 68 public static final int FAILED_ATTEMPTS_BEFORE_TIMEOUT = 5; 69 70 /** 71 * The number of incorrect attempts before which we fall back on an alternative 72 * method of verifying the user, and resetting their lock pattern. 73 */ 74 public static final int FAILED_ATTEMPTS_BEFORE_RESET = 20; 75 76 /** 77 * How long the user is prevented from trying again after entering the 78 * wrong pattern too many times. 79 */ 80 public static final long FAILED_ATTEMPT_TIMEOUT_MS = 30000L; 81 82 /** 83 * The interval of the countdown for showing progress of the lockout. 84 */ 85 public static final long FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS = 1000L; 86 87 88 /** 89 * This dictates when we start telling the user that continued failed attempts will wipe 90 * their device. 91 */ 92 public static final int FAILED_ATTEMPTS_BEFORE_WIPE_GRACE = 5; 93 94 /** 95 * The minimum number of dots in a valid pattern. 96 */ 97 public static final int MIN_LOCK_PATTERN_SIZE = 4; 98 99 /** 100 * The minimum number of dots the user must include in a wrong pattern 101 * attempt for it to be counted against the counts that affect 102 * {@link #FAILED_ATTEMPTS_BEFORE_TIMEOUT} and {@link #FAILED_ATTEMPTS_BEFORE_RESET} 103 */ 104 public static final int MIN_PATTERN_REGISTER_FAIL = MIN_LOCK_PATTERN_SIZE; 105 106 private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently"; 107 private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline"; 108 private final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen"; 109 public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type"; 110 public static final String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate"; 111 private final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt"; 112 private final static String DISABLE_LOCKSCREEN_KEY = "lockscreen.disabled"; 113 public final static String LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK 114 = "lockscreen.biometric_weak_fallback"; 115 116 private final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory"; 117 118 private final Context mContext; 119 private final ContentResolver mContentResolver; 120 private DevicePolicyManager mDevicePolicyManager; 121 private static String sLockPatternFilename; 122 private static String sLockPasswordFilename; 123 124 private static final AtomicBoolean sHaveNonZeroPatternFile = new AtomicBoolean(false); 125 private static final AtomicBoolean sHaveNonZeroPasswordFile = new AtomicBoolean(false); 126 127 private static FileObserver sPasswordObserver; 128 129 private static class PasswordFileObserver extends FileObserver { 130 public PasswordFileObserver(String path, int mask) { 131 super(path, mask); 132 } 133 134 @Override 135 public void onEvent(int event, String path) { 136 if (LOCK_PATTERN_FILE.equals(path)) { 137 Log.d(TAG, "lock pattern file changed"); 138 sHaveNonZeroPatternFile.set(new File(sLockPatternFilename).length() > 0); 139 } else if (LOCK_PASSWORD_FILE.equals(path)) { 140 Log.d(TAG, "lock password file changed"); 141 sHaveNonZeroPasswordFile.set(new File(sLockPasswordFilename).length() > 0); 142 } 143 } 144 } 145 146 public DevicePolicyManager getDevicePolicyManager() { 147 if (mDevicePolicyManager == null) { 148 mDevicePolicyManager = 149 (DevicePolicyManager)mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); 150 if (mDevicePolicyManager == null) { 151 Log.e(TAG, "Can't get DevicePolicyManagerService: is it running?", 152 new IllegalStateException("Stack trace:")); 153 } 154 } 155 return mDevicePolicyManager; 156 } 157 /** 158 * @param contentResolver Used to look up and save settings. 159 */ 160 public LockPatternUtils(Context context) { 161 mContext = context; 162 mContentResolver = context.getContentResolver(); 163 164 // Initialize the location of gesture & PIN lock files 165 if (sLockPatternFilename == null) { 166 String dataSystemDirectory = 167 android.os.Environment.getDataDirectory().getAbsolutePath() + 168 SYSTEM_DIRECTORY; 169 sLockPatternFilename = dataSystemDirectory + LOCK_PATTERN_FILE; 170 sLockPasswordFilename = dataSystemDirectory + LOCK_PASSWORD_FILE; 171 sHaveNonZeroPatternFile.set(new File(sLockPatternFilename).length() > 0); 172 sHaveNonZeroPasswordFile.set(new File(sLockPasswordFilename).length() > 0); 173 int fileObserverMask = FileObserver.CLOSE_WRITE | FileObserver.DELETE | 174 FileObserver.MOVED_TO | FileObserver.CREATE; 175 sPasswordObserver = new PasswordFileObserver(dataSystemDirectory, fileObserverMask); 176 sPasswordObserver.startWatching(); 177 } 178 } 179 180 public int getRequestedMinimumPasswordLength() { 181 return getDevicePolicyManager().getPasswordMinimumLength(null); 182 } 183 184 185 /** 186 * Gets the device policy password mode. If the mode is non-specific, returns 187 * MODE_PATTERN which allows the user to choose anything. 188 */ 189 public int getRequestedPasswordQuality() { 190 return getDevicePolicyManager().getPasswordQuality(null); 191 } 192 193 public int getRequestedPasswordHistoryLength() { 194 return getDevicePolicyManager().getPasswordHistoryLength(null); 195 } 196 197 public int getRequestedPasswordMinimumLetters() { 198 return getDevicePolicyManager().getPasswordMinimumLetters(null); 199 } 200 201 public int getRequestedPasswordMinimumUpperCase() { 202 return getDevicePolicyManager().getPasswordMinimumUpperCase(null); 203 } 204 205 public int getRequestedPasswordMinimumLowerCase() { 206 return getDevicePolicyManager().getPasswordMinimumLowerCase(null); 207 } 208 209 public int getRequestedPasswordMinimumNumeric() { 210 return getDevicePolicyManager().getPasswordMinimumNumeric(null); 211 } 212 213 public int getRequestedPasswordMinimumSymbols() { 214 return getDevicePolicyManager().getPasswordMinimumSymbols(null); 215 } 216 217 public int getRequestedPasswordMinimumNonLetter() { 218 return getDevicePolicyManager().getPasswordMinimumNonLetter(null); 219 } 220 /** 221 * Returns the actual password mode, as set by keyguard after updating the password. 222 * 223 * @return 224 */ 225 public void reportFailedPasswordAttempt() { 226 getDevicePolicyManager().reportFailedPasswordAttempt(); 227 } 228 229 public void reportSuccessfulPasswordAttempt() { 230 getDevicePolicyManager().reportSuccessfulPasswordAttempt(); 231 } 232 233 /** 234 * Check to see if a pattern matches the saved pattern. If no pattern exists, 235 * always returns true. 236 * @param pattern The pattern to check. 237 * @return Whether the pattern matches the stored one. 238 */ 239 public boolean checkPattern(List<LockPatternView.Cell> pattern) { 240 try { 241 // Read all the bytes from the file 242 RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "r"); 243 final byte[] stored = new byte[(int) raf.length()]; 244 int got = raf.read(stored, 0, stored.length); 245 raf.close(); 246 if (got <= 0) { 247 return true; 248 } 249 // Compare the hash from the file with the entered pattern's hash 250 return Arrays.equals(stored, LockPatternUtils.patternToHash(pattern)); 251 } catch (FileNotFoundException fnfe) { 252 return true; 253 } catch (IOException ioe) { 254 return true; 255 } 256 } 257 258 /** 259 * Check to see if a password matches the saved password. If no password exists, 260 * always returns true. 261 * @param password The password to check. 262 * @return Whether the password matches the stored one. 263 */ 264 public boolean checkPassword(String password) { 265 try { 266 // Read all the bytes from the file 267 RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "r"); 268 final byte[] stored = new byte[(int) raf.length()]; 269 int got = raf.read(stored, 0, stored.length); 270 raf.close(); 271 if (got <= 0) { 272 return true; 273 } 274 // Compare the hash from the file with the entered password's hash 275 return Arrays.equals(stored, passwordToHash(password)); 276 } catch (FileNotFoundException fnfe) { 277 return true; 278 } catch (IOException ioe) { 279 return true; 280 } 281 } 282 283 /** 284 * Check to see if a password matches any of the passwords stored in the 285 * password history. 286 * 287 * @param password The password to check. 288 * @return Whether the password matches any in the history. 289 */ 290 public boolean checkPasswordHistory(String password) { 291 String passwordHashString = new String(passwordToHash(password)); 292 String passwordHistory = getString(PASSWORD_HISTORY_KEY); 293 if (passwordHistory == null) { 294 return false; 295 } 296 // Password History may be too long... 297 int passwordHashLength = passwordHashString.length(); 298 int passwordHistoryLength = getRequestedPasswordHistoryLength(); 299 if(passwordHistoryLength == 0) { 300 return false; 301 } 302 int neededPasswordHistoryLength = passwordHashLength * passwordHistoryLength 303 + passwordHistoryLength - 1; 304 if (passwordHistory.length() > neededPasswordHistoryLength) { 305 passwordHistory = passwordHistory.substring(0, neededPasswordHistoryLength); 306 } 307 return passwordHistory.contains(passwordHashString); 308 } 309 310 /** 311 * Check to see if the user has stored a lock pattern. 312 * @return Whether a saved pattern exists. 313 */ 314 public boolean savedPatternExists() { 315 return sHaveNonZeroPatternFile.get(); 316 } 317 318 /** 319 * Check to see if the user has stored a lock pattern. 320 * @return Whether a saved pattern exists. 321 */ 322 public boolean savedPasswordExists() { 323 return sHaveNonZeroPasswordFile.get(); 324 } 325 326 /** 327 * Return true if the user has ever chosen a pattern. This is true even if the pattern is 328 * currently cleared. 329 * 330 * @return True if the user has ever chosen a pattern. 331 */ 332 public boolean isPatternEverChosen() { 333 return getBoolean(PATTERN_EVER_CHOSEN_KEY); 334 } 335 336 /** 337 * Used by device policy manager to validate the current password 338 * information it has. 339 */ 340 public int getActivePasswordQuality() { 341 int activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; 342 switch (getKeyguardStoredPasswordQuality()) { 343 case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: 344 if (isLockPatternEnabled()) { 345 activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; 346 } 347 break; 348 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: 349 if (isLockPasswordEnabled()) { 350 activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 351 } 352 break; 353 case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: 354 if (isLockPasswordEnabled()) { 355 activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; 356 } 357 break; 358 case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: 359 if (isLockPasswordEnabled()) { 360 activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; 361 } 362 break; 363 case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: 364 if (isLockPasswordEnabled()) { 365 activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; 366 } 367 break; 368 } 369 return activePasswordQuality; 370 } 371 372 /** 373 * Clear any lock pattern or password. 374 */ 375 public void clearLock(boolean isFallback) { 376 if(!isFallback) deleteGallery(); 377 saveLockPassword(null, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); 378 setLockPatternEnabled(false); 379 saveLockPattern(null); 380 setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); 381 setLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED); 382 } 383 384 /** 385 * Disable showing lock screen at all when the DevicePolicyManager allows it. 386 * This is only meaningful if pattern, pin or password are not set. 387 * 388 * @param disable Disables lock screen when true 389 */ 390 public void setLockScreenDisabled(boolean disable) { 391 setLong(DISABLE_LOCKSCREEN_KEY, disable ? 1 : 0); 392 } 393 394 /** 395 * Determine if LockScreen can be disabled. This is used, for example, to tell if we should 396 * show LockScreen or go straight to the home screen. 397 * 398 * @return true if lock screen is can be disabled 399 */ 400 public boolean isLockScreenDisabled() { 401 return !isSecure() && getLong(DISABLE_LOCKSCREEN_KEY, 0) != 0; 402 } 403 404 /** 405 * Save a lock pattern. 406 * @param pattern The new pattern to save. 407 */ 408 public void saveLockPattern(List<LockPatternView.Cell> pattern) { 409 this.saveLockPattern(pattern, false); 410 } 411 412 /** 413 * Calls back SetupFaceLock to save the temporary gallery file if this is the backup lock. 414 * This doesn't have to verify that biometric is enabled because it's only called in that case 415 */ 416 void moveTempGallery() { 417 Intent intent = new Intent().setClassName("com.android.facelock", 418 "com.android.facelock.SetupFaceLock"); 419 intent.putExtra("moveTempGallery", true); 420 mContext.startActivity(intent); 421 } 422 423 /** 424 * Calls back SetupFaceLock to delete the temporary gallery file 425 */ 426 public void deleteTempGallery() { 427 Intent intent = new Intent().setClassName("com.android.facelock", 428 "com.android.facelock.SetupFaceLock"); 429 intent.putExtra("deleteTempGallery", true); 430 mContext.startActivity(intent); 431 } 432 433 /** 434 * Calls back SetupFaceLock to delete the gallery file when the lock type is changed 435 */ 436 void deleteGallery() { 437 if(isBiometricEnabled()) { 438 Intent intent = new Intent().setClassName("com.android.facelock", 439 "com.android.facelock.SetupFaceLock"); 440 intent.putExtra("deleteGallery", true); 441 mContext.startActivity(intent); 442 } 443 } 444 445 /** 446 * Save a lock pattern. 447 * @param pattern The new pattern to save. 448 * @param isFallback Specifies if this is a fallback to biometric weak 449 */ 450 public void saveLockPattern(List<LockPatternView.Cell> pattern, boolean isFallback) { 451 // Compute the hash 452 final byte[] hash = LockPatternUtils.patternToHash(pattern); 453 try { 454 // Write the hash to file 455 RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "rw"); 456 // Truncate the file if pattern is null, to clear the lock 457 if (pattern == null) { 458 raf.setLength(0); 459 } else { 460 raf.write(hash, 0, hash.length); 461 } 462 raf.close(); 463 DevicePolicyManager dpm = getDevicePolicyManager(); 464 KeyStore keyStore = KeyStore.getInstance(); 465 if (pattern != null) { 466 keyStore.password(patternToString(pattern)); 467 setBoolean(PATTERN_EVER_CHOSEN_KEY, true); 468 if (!isFallback) { 469 deleteGallery(); 470 setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); 471 } else { 472 setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK); 473 setLong(PASSWORD_TYPE_ALTERNATE_KEY, 474 DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); 475 moveTempGallery(); 476 } 477 dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern 478 .size(), 0, 0, 0, 0, 0, 0); 479 } else { 480 if (keyStore.isEmpty()) { 481 keyStore.reset(); 482 } 483 dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 484 0, 0, 0, 0, 0); 485 } 486 } catch (FileNotFoundException fnfe) { 487 // Cant do much, unless we want to fail over to using the settings 488 // provider 489 Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename); 490 } catch (IOException ioe) { 491 // Cant do much 492 Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename); 493 } 494 } 495 496 /** 497 * Compute the password quality from the given password string. 498 */ 499 static public int computePasswordQuality(String password) { 500 boolean hasDigit = false; 501 boolean hasNonDigit = false; 502 final int len = password.length(); 503 for (int i = 0; i < len; i++) { 504 if (Character.isDigit(password.charAt(i))) { 505 hasDigit = true; 506 } else { 507 hasNonDigit = true; 508 } 509 } 510 511 if (hasNonDigit && hasDigit) { 512 return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; 513 } 514 if (hasNonDigit) { 515 return DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; 516 } 517 if (hasDigit) { 518 return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 519 } 520 return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; 521 } 522 523 /** Update the encryption password if it is enabled **/ 524 private void updateEncryptionPassword(String password) { 525 DevicePolicyManager dpm = getDevicePolicyManager(); 526 if (dpm.getStorageEncryptionStatus() != DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) { 527 return; 528 } 529 530 IBinder service = ServiceManager.getService("mount"); 531 if (service == null) { 532 Log.e(TAG, "Could not find the mount service to update the encryption password"); 533 return; 534 } 535 536 IMountService mountService = IMountService.Stub.asInterface(service); 537 try { 538 mountService.changeEncryptionPassword(password); 539 } catch (RemoteException e) { 540 Log.e(TAG, "Error changing encryption password", e); 541 } 542 } 543 544 /** 545 * Save a lock password. Does not ensure that the password is as good 546 * as the requested mode, but will adjust the mode to be as good as the 547 * pattern. 548 * @param password The password to save 549 * @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)} 550 */ 551 public void saveLockPassword(String password, int quality) { 552 this.saveLockPassword(password, quality, false); 553 } 554 555 /** 556 * Save a lock password. Does not ensure that the password is as good 557 * as the requested mode, but will adjust the mode to be as good as the 558 * pattern. 559 * @param password The password to save 560 * @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)} 561 * @param isFallback Specifies if this is a fallback to biometric weak 562 */ 563 public void saveLockPassword(String password, int quality, boolean isFallback) { 564 // Compute the hash 565 final byte[] hash = passwordToHash(password); 566 try { 567 // Write the hash to file 568 RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "rw"); 569 // Truncate the file if pattern is null, to clear the lock 570 if (password == null) { 571 raf.setLength(0); 572 } else { 573 raf.write(hash, 0, hash.length); 574 } 575 raf.close(); 576 DevicePolicyManager dpm = getDevicePolicyManager(); 577 KeyStore keyStore = KeyStore.getInstance(); 578 if (password != null) { 579 // Update the encryption password. 580 updateEncryptionPassword(password); 581 582 // Update the keystore password 583 keyStore.password(password); 584 585 int computedQuality = computePasswordQuality(password); 586 if (!isFallback) { 587 deleteGallery(); 588 setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality)); 589 } else { 590 setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK); 591 setLong(PASSWORD_TYPE_ALTERNATE_KEY, Math.max(quality, computedQuality)); 592 moveTempGallery(); 593 } 594 if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { 595 int letters = 0; 596 int uppercase = 0; 597 int lowercase = 0; 598 int numbers = 0; 599 int symbols = 0; 600 int nonletter = 0; 601 for (int i = 0; i < password.length(); i++) { 602 char c = password.charAt(i); 603 if (c >= 'A' && c <= 'Z') { 604 letters++; 605 uppercase++; 606 } else if (c >= 'a' && c <= 'z') { 607 letters++; 608 lowercase++; 609 } else if (c >= '0' && c <= '9') { 610 numbers++; 611 nonletter++; 612 } else { 613 symbols++; 614 nonletter++; 615 } 616 } 617 dpm.setActivePasswordState(Math.max(quality, computedQuality), password 618 .length(), letters, uppercase, lowercase, numbers, symbols, nonletter); 619 } else { 620 // The password is not anything. 621 dpm.setActivePasswordState( 622 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0); 623 } 624 // Add the password to the password history. We assume all 625 // password 626 // hashes have the same length for simplicity of implementation. 627 String passwordHistory = getString(PASSWORD_HISTORY_KEY); 628 if (passwordHistory == null) { 629 passwordHistory = new String(); 630 } 631 int passwordHistoryLength = getRequestedPasswordHistoryLength(); 632 if (passwordHistoryLength == 0) { 633 passwordHistory = ""; 634 } else { 635 passwordHistory = new String(hash) + "," + passwordHistory; 636 // Cut it to contain passwordHistoryLength hashes 637 // and passwordHistoryLength -1 commas. 638 passwordHistory = passwordHistory.substring(0, Math.min(hash.length 639 * passwordHistoryLength + passwordHistoryLength - 1, passwordHistory 640 .length())); 641 } 642 setString(PASSWORD_HISTORY_KEY, passwordHistory); 643 } else { 644 // Conditionally reset the keystore if empty. If 645 // non-empty, we are just switching key guard type 646 if (keyStore.isEmpty()) { 647 keyStore.reset(); 648 } 649 dpm.setActivePasswordState( 650 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0); 651 } 652 } catch (FileNotFoundException fnfe) { 653 // Cant do much, unless we want to fail over to using the settings provider 654 Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename); 655 } catch (IOException ioe) { 656 // Cant do much 657 Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename); 658 } 659 } 660 661 /** 662 * Retrieves the quality mode we're in. 663 * {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)} 664 * 665 * @return stored password quality 666 */ 667 public int getKeyguardStoredPasswordQuality() { 668 int quality = 669 (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); 670 // If the user has chosen to use weak biometric sensor, then return the backup locking 671 // method and treat biometric as a special case. 672 if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) { 673 quality = 674 (int) getLong(PASSWORD_TYPE_ALTERNATE_KEY, 675 DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); 676 } 677 return quality; 678 } 679 680 public boolean usingBiometricWeak() { 681 int quality = 682 (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); 683 return quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK; 684 } 685 686 /** 687 * Deserialize a pattern. 688 * @param string The pattern serialized with {@link #patternToString} 689 * @return The pattern. 690 */ 691 public static List<LockPatternView.Cell> stringToPattern(String string) { 692 List<LockPatternView.Cell> result = Lists.newArrayList(); 693 694 final byte[] bytes = string.getBytes(); 695 for (int i = 0; i < bytes.length; i++) { 696 byte b = bytes[i]; 697 result.add(LockPatternView.Cell.of(b / 3, b % 3)); 698 } 699 return result; 700 } 701 702 /** 703 * Serialize a pattern. 704 * @param pattern The pattern. 705 * @return The pattern in string form. 706 */ 707 public static String patternToString(List<LockPatternView.Cell> pattern) { 708 if (pattern == null) { 709 return ""; 710 } 711 final int patternSize = pattern.size(); 712 713 byte[] res = new byte[patternSize]; 714 for (int i = 0; i < patternSize; i++) { 715 LockPatternView.Cell cell = pattern.get(i); 716 res[i] = (byte) (cell.getRow() * 3 + cell.getColumn()); 717 } 718 return new String(res); 719 } 720 721 /* 722 * Generate an SHA-1 hash for the pattern. Not the most secure, but it is 723 * at least a second level of protection. First level is that the file 724 * is in a location only readable by the system process. 725 * @param pattern the gesture pattern. 726 * @return the hash of the pattern in a byte array. 727 */ 728 private static byte[] patternToHash(List<LockPatternView.Cell> pattern) { 729 if (pattern == null) { 730 return null; 731 } 732 733 final int patternSize = pattern.size(); 734 byte[] res = new byte[patternSize]; 735 for (int i = 0; i < patternSize; i++) { 736 LockPatternView.Cell cell = pattern.get(i); 737 res[i] = (byte) (cell.getRow() * 3 + cell.getColumn()); 738 } 739 try { 740 MessageDigest md = MessageDigest.getInstance("SHA-1"); 741 byte[] hash = md.digest(res); 742 return hash; 743 } catch (NoSuchAlgorithmException nsa) { 744 return res; 745 } 746 } 747 748 private String getSalt() { 749 long salt = getLong(LOCK_PASSWORD_SALT_KEY, 0); 750 if (salt == 0) { 751 try { 752 salt = SecureRandom.getInstance("SHA1PRNG").nextLong(); 753 setLong(LOCK_PASSWORD_SALT_KEY, salt); 754 Log.v(TAG, "Initialized lock password salt"); 755 } catch (NoSuchAlgorithmException e) { 756 // Throw an exception rather than storing a password we'll never be able to recover 757 throw new IllegalStateException("Couldn't get SecureRandom number", e); 758 } 759 } 760 return Long.toHexString(salt); 761 } 762 763 /* 764 * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash. 765 * Not the most secure, but it is at least a second level of protection. First level is that 766 * the file is in a location only readable by the system process. 767 * @param password the gesture pattern. 768 * @return the hash of the pattern in a byte array. 769 */ 770 public byte[] passwordToHash(String password) { 771 if (password == null) { 772 return null; 773 } 774 String algo = null; 775 byte[] hashed = null; 776 try { 777 byte[] saltedPassword = (password + getSalt()).getBytes(); 778 byte[] sha1 = MessageDigest.getInstance(algo = "SHA-1").digest(saltedPassword); 779 byte[] md5 = MessageDigest.getInstance(algo = "MD5").digest(saltedPassword); 780 hashed = (toHex(sha1) + toHex(md5)).getBytes(); 781 } catch (NoSuchAlgorithmException e) { 782 Log.w(TAG, "Failed to encode string because of missing algorithm: " + algo); 783 } 784 return hashed; 785 } 786 787 private static String toHex(byte[] ary) { 788 final String hex = "0123456789ABCDEF"; 789 String ret = ""; 790 for (int i = 0; i < ary.length; i++) { 791 ret += hex.charAt((ary[i] >> 4) & 0xf); 792 ret += hex.charAt(ary[i] & 0xf); 793 } 794 return ret; 795 } 796 797 /** 798 * @return Whether the lock password is enabled, or if it is set as a backup for biometric weak 799 */ 800 public boolean isLockPasswordEnabled() { 801 long mode = getLong(PASSWORD_TYPE_KEY, 0); 802 long backupMode = getLong(PASSWORD_TYPE_ALTERNATE_KEY, 0); 803 final boolean passwordEnabled = mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC 804 || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC 805 || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC 806 || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; 807 final boolean backupEnabled = backupMode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC 808 || backupMode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC 809 || backupMode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC 810 || backupMode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; 811 812 return savedPasswordExists() && (passwordEnabled || 813 (isBiometricEnabled() && backupEnabled)); 814 } 815 816 /** 817 * @return Whether the lock pattern is enabled, or if it is set as a backup for biometric weak 818 */ 819 public boolean isLockPatternEnabled() { 820 final boolean backupEnabled = 821 getLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) 822 == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; 823 824 return getBoolean(Settings.Secure.LOCK_PATTERN_ENABLED) 825 && (getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) 826 == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING || 827 (isBiometricEnabled() && backupEnabled)); 828 } 829 830 /** 831 * @return Whether biometric weak lock is enabled. 832 */ 833 public boolean isBiometricEnabled() { 834 // TODO: check if it's installed 835 return getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) 836 == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK; 837 } 838 839 /** 840 * Set whether the lock pattern is enabled. 841 */ 842 public void setLockPatternEnabled(boolean enabled) { 843 setBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, enabled); 844 } 845 846 /** 847 * @return Whether the visible pattern is enabled. 848 */ 849 public boolean isVisiblePatternEnabled() { 850 return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE); 851 } 852 853 /** 854 * Set whether the visible pattern is enabled. 855 */ 856 public void setVisiblePatternEnabled(boolean enabled) { 857 setBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, enabled); 858 } 859 860 /** 861 * @return Whether tactile feedback for the pattern is enabled. 862 */ 863 public boolean isTactileFeedbackEnabled() { 864 return getBoolean(Settings.Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED); 865 } 866 867 /** 868 * Set whether tactile feedback for the pattern is enabled. 869 */ 870 public void setTactileFeedbackEnabled(boolean enabled) { 871 setBoolean(Settings.Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED, enabled); 872 } 873 874 /** 875 * Set and store the lockout deadline, meaning the user can't attempt his/her unlock 876 * pattern until the deadline has passed. 877 * @return the chosen deadline. 878 */ 879 public long setLockoutAttemptDeadline() { 880 final long deadline = SystemClock.elapsedRealtime() + FAILED_ATTEMPT_TIMEOUT_MS; 881 setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline); 882 return deadline; 883 } 884 885 /** 886 * @return The elapsed time in millis in the future when the user is allowed to 887 * attempt to enter his/her lock pattern, or 0 if the user is welcome to 888 * enter a pattern. 889 */ 890 public long getLockoutAttemptDeadline() { 891 final long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L); 892 final long now = SystemClock.elapsedRealtime(); 893 if (deadline < now || deadline > (now + FAILED_ATTEMPT_TIMEOUT_MS)) { 894 return 0L; 895 } 896 return deadline; 897 } 898 899 /** 900 * @return Whether the user is permanently locked out until they verify their 901 * credentials. Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed 902 * attempts. 903 */ 904 public boolean isPermanentlyLocked() { 905 return getBoolean(LOCKOUT_PERMANENT_KEY); 906 } 907 908 /** 909 * Set the state of whether the device is permanently locked, meaning the user 910 * must authenticate via other means. 911 * 912 * @param locked Whether the user is permanently locked out until they verify their 913 * credentials. Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed 914 * attempts. 915 */ 916 public void setPermanentlyLocked(boolean locked) { 917 setBoolean(LOCKOUT_PERMANENT_KEY, locked); 918 } 919 920 public boolean isEmergencyCallCapable() { 921 return mContext.getResources().getBoolean( 922 com.android.internal.R.bool.config_voice_capable); 923 } 924 925 public boolean isPukUnlockScreenEnable() { 926 return mContext.getResources().getBoolean( 927 com.android.internal.R.bool.config_enable_puk_unlock_screen); 928 } 929 930 /** 931 * @return A formatted string of the next alarm (for showing on the lock screen), 932 * or null if there is no next alarm. 933 */ 934 public String getNextAlarm() { 935 String nextAlarm = Settings.System.getString(mContentResolver, 936 Settings.System.NEXT_ALARM_FORMATTED); 937 if (nextAlarm == null || TextUtils.isEmpty(nextAlarm)) { 938 return null; 939 } 940 return nextAlarm; 941 } 942 943 private boolean getBoolean(String secureSettingKey) { 944 return 1 == 945 android.provider.Settings.Secure.getInt(mContentResolver, secureSettingKey, 0); 946 } 947 948 private void setBoolean(String secureSettingKey, boolean enabled) { 949 android.provider.Settings.Secure.putInt(mContentResolver, secureSettingKey, 950 enabled ? 1 : 0); 951 } 952 953 private long getLong(String secureSettingKey, long def) { 954 return android.provider.Settings.Secure.getLong(mContentResolver, secureSettingKey, def); 955 } 956 957 private void setLong(String secureSettingKey, long value) { 958 android.provider.Settings.Secure.putLong(mContentResolver, secureSettingKey, value); 959 } 960 961 private String getString(String secureSettingKey) { 962 return android.provider.Settings.Secure.getString(mContentResolver, secureSettingKey); 963 } 964 965 private void setString(String secureSettingKey, String value) { 966 android.provider.Settings.Secure.putString(mContentResolver, secureSettingKey, value); 967 } 968 969 public boolean isSecure() { 970 long mode = getKeyguardStoredPasswordQuality(); 971 final boolean isPattern = mode == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; 972 final boolean isPassword = mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC 973 || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC 974 || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC 975 || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; 976 final boolean secure = isPattern && isLockPatternEnabled() && savedPatternExists() 977 || isPassword && savedPasswordExists(); 978 return secure; 979 } 980 981 /** 982 * Sets the emergency button visibility based on isEmergencyCallCapable(). 983 * 984 * If the emergency button is visible, sets the text on the emergency button 985 * to indicate what action will be taken. 986 * 987 * If there's currently a call in progress, the button will take them to the call 988 * @param button the button to update 989 * @param the phone state: 990 * {@link TelephonyManager#CALL_STATE_IDLE} 991 * {@link TelephonyManager#CALL_STATE_RINGING} 992 * {@link TelephonyManager#CALL_STATE_OFFHOOK} 993 * @param showIfCapable indicates whether the button should be shown if emergency calls are 994 * possible on the device 995 */ 996 public void updateEmergencyCallButtonState(Button button, int phoneState, 997 boolean showIfCapable) { 998 if (isEmergencyCallCapable() && showIfCapable) { 999 button.setVisibility(View.VISIBLE); 1000 } else { 1001 button.setVisibility(View.GONE); 1002 return; 1003 } 1004 1005 int textId; 1006 if (phoneState == TelephonyManager.CALL_STATE_OFFHOOK) { 1007 // show "return to call" text and show phone icon 1008 textId = R.string.lockscreen_return_to_call; 1009 int phoneCallIcon = R.drawable.stat_sys_phone_call; 1010 button.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0); 1011 } else { 1012 textId = R.string.lockscreen_emergency_call; 1013 int emergencyIcon = R.drawable.ic_emergency; 1014 button.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0); 1015 } 1016 button.setText(textId); 1017 } 1018 1019 /** 1020 * Resumes a call in progress. Typically launched from the EmergencyCall button 1021 * on various lockscreens. 1022 * 1023 * @return true if we were able to tell InCallScreen to show. 1024 */ 1025 public boolean resumeCall() { 1026 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone")); 1027 try { 1028 if (phone != null && phone.showCallScreen()) { 1029 return true; 1030 } 1031 } catch (RemoteException e) { 1032 // What can we do? 1033 } 1034 return false; 1035 } 1036} 1037