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