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