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