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