LockPatternUtils.java revision 5b0fb3a7e8070ed366a85acc1904d2f34030445d
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 android.app.DevicePolicyManager; 20import android.content.ComponentName; 21import android.content.ContentResolver; 22import android.content.Context; 23import android.os.SystemClock; 24import android.provider.Settings; 25import android.security.MessageDigest; 26import android.text.TextUtils; 27import android.util.Log; 28 29import com.google.android.collect.Lists; 30 31import java.io.FileNotFoundException; 32import java.io.IOException; 33import java.io.RandomAccessFile; 34import java.security.NoSuchAlgorithmException; 35import java.security.SecureRandom; 36import java.util.Arrays; 37import java.util.List; 38 39/** 40 * Utilities for the lock patten and its settings. 41 */ 42public class LockPatternUtils { 43 44 private static final String TAG = "LockPatternUtils"; 45 46 private static final String LOCK_PATTERN_FILE = "/system/gesture.key"; 47 private static final String LOCK_PASSWORD_FILE = "/system/password.key"; 48 49 /** 50 * The maximum number of incorrect attempts before the user is prevented 51 * from trying again for {@link #FAILED_ATTEMPT_TIMEOUT_MS}. 52 */ 53 public static final int FAILED_ATTEMPTS_BEFORE_TIMEOUT = 5; 54 55 /** 56 * The number of incorrect attempts before which we fall back on an alternative 57 * method of verifying the user, and resetting their lock pattern. 58 */ 59 public static final int FAILED_ATTEMPTS_BEFORE_RESET = 20; 60 61 /** 62 * How long the user is prevented from trying again after entering the 63 * wrong pattern too many times. 64 */ 65 public static final long FAILED_ATTEMPT_TIMEOUT_MS = 30000L; 66 67 /** 68 * The interval of the countdown for showing progress of the lockout. 69 */ 70 public static final long FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS = 1000L; 71 72 /** 73 * The minimum number of dots in a valid pattern. 74 */ 75 public static final int MIN_LOCK_PATTERN_SIZE = 4; 76 77 /** 78 * Type of password being stored. 79 * pattern = pattern screen 80 * pin = digit-only password 81 * password = alphanumeric password 82 */ 83 public static final int MODE_UNSPECIFIED = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; 84 public static final int MODE_PATTERN = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; 85 public static final int MODE_PIN = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 86 public static final int MODE_PASSWORD = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; 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 101 private final Context mContext; 102 private final ContentResolver mContentResolver; 103 private DevicePolicyManager mDevicePolicyManager; 104 private static String sLockPatternFilename; 105 private static String sLockPasswordFilename; 106 107 DevicePolicyManager getDevicePolicyManager() { 108 if (mDevicePolicyManager == null) { 109 mDevicePolicyManager = 110 (DevicePolicyManager)mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); 111 if (mDevicePolicyManager == null) { 112 Log.e(TAG, "Can't get DevicePolicyManagerService: is it running?", 113 new IllegalStateException("Stack trace:")); 114 } 115 } 116 return mDevicePolicyManager; 117 } 118 /** 119 * @param contentResolver Used to look up and save settings. 120 */ 121 public LockPatternUtils(Context context) { 122 mContext = context; 123 mContentResolver = context.getContentResolver(); 124 mDevicePolicyManager = getDevicePolicyManager(); 125 // Initialize the location of gesture lock file 126 if (sLockPatternFilename == null) { 127 sLockPatternFilename = android.os.Environment.getDataDirectory() 128 .getAbsolutePath() + LOCK_PATTERN_FILE; 129 sLockPasswordFilename = android.os.Environment.getDataDirectory() 130 .getAbsolutePath() + LOCK_PASSWORD_FILE; 131 } 132 133 } 134 135 public int getRequestedMinimumPasswordLength() { 136 return getDevicePolicyManager().getPasswordMinimumLength(null); 137 } 138 139 /** 140 * Gets the device policy password mode. If the mode is non-specific, returns 141 * MODE_PATTERN which allows the user to choose anything. 142 * 143 * @return 144 */ 145 public int getRequestedPasswordMode() { 146 int policyMode = getDevicePolicyManager().getPasswordQuality(null); 147 switch (policyMode) { 148 case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: 149 return MODE_PASSWORD; 150 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: 151 return MODE_PIN; 152 case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED: 153 return MODE_PATTERN; 154 } 155 return MODE_PATTERN; 156 } 157 158 /** 159 * Returns the actual password mode, as set by keyguard after updating the password. 160 * 161 * @return 162 */ 163 public void reportFailedPasswordAttempt() { 164 getDevicePolicyManager().reportFailedPasswordAttempt(); 165 } 166 167 public void reportSuccessfulPasswordAttempt() { 168 getDevicePolicyManager().reportSuccessfulPasswordAttempt(); 169 } 170 171 public void setActivePasswordState(int mode, int length) { 172 int policyMode = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; 173 switch (mode) { 174 case MODE_PATTERN: 175 policyMode = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; 176 break; 177 case MODE_PIN: 178 policyMode = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 179 break; 180 case MODE_PASSWORD: 181 policyMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; 182 break; 183 } 184 getDevicePolicyManager().setActivePasswordState(policyMode, length); 185 } 186 187 /** 188 * Check to see if a pattern matches the saved pattern. If no pattern exists, 189 * always returns true. 190 * @param pattern The pattern to check. 191 * @return Whether the pattern matches the stored one. 192 */ 193 public boolean checkPattern(List<LockPatternView.Cell> pattern) { 194 try { 195 // Read all the bytes from the file 196 RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "r"); 197 final byte[] stored = new byte[(int) raf.length()]; 198 int got = raf.read(stored, 0, stored.length); 199 raf.close(); 200 if (got <= 0) { 201 return true; 202 } 203 // Compare the hash from the file with the entered pattern's hash 204 return Arrays.equals(stored, LockPatternUtils.patternToHash(pattern)); 205 } catch (FileNotFoundException fnfe) { 206 return true; 207 } catch (IOException ioe) { 208 return true; 209 } 210 } 211 212 /** 213 * Check to see if a password matches the saved password. If no password exists, 214 * always returns true. 215 * @param password The password to check. 216 * @return Whether the password matches the stored one. 217 */ 218 public boolean checkPassword(String password) { 219 try { 220 // Read all the bytes from the file 221 RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "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 password's hash 229 return Arrays.equals(stored, passwordToHash(password)); 230 } catch (FileNotFoundException fnfe) { 231 return true; 232 } catch (IOException ioe) { 233 return true; 234 } 235 } 236 237 /** 238 * Checks to see if the given file exists and contains any data. Returns true if it does, 239 * false otherwise. 240 * @param filename 241 * @return true if file exists and is non-empty. 242 */ 243 private boolean nonEmptyFileExists(String filename) { 244 try { 245 // Check if we can read a byte from the file 246 RandomAccessFile raf = new RandomAccessFile(filename, "r"); 247 byte first = raf.readByte(); 248 raf.close(); 249 return true; 250 } catch (FileNotFoundException fnfe) { 251 return false; 252 } catch (IOException ioe) { 253 return false; 254 } 255 } 256 257 /** 258 * Check to see if the user has stored a lock pattern. 259 * @return Whether a saved pattern exists. 260 */ 261 public boolean savedPatternExists() { 262 return nonEmptyFileExists(sLockPatternFilename); 263 } 264 265 /** 266 * Check to see if the user has stored a lock pattern. 267 * @return Whether a saved pattern exists. 268 */ 269 public boolean savedPasswordExists() { 270 return nonEmptyFileExists(sLockPasswordFilename); 271 } 272 273 /** 274 * Return true if the user has ever chosen a pattern. This is true even if the pattern is 275 * currently cleared. 276 * 277 * @return True if the user has ever chosen a pattern. 278 */ 279 public boolean isPatternEverChosen() { 280 return getBoolean(PATTERN_EVER_CHOSEN_KEY); 281 } 282 283 /** 284 * Clear any lock pattern or password. 285 */ 286 public void clearLock() { 287 saveLockPassword(null, LockPatternUtils.MODE_PATTERN); 288 setLockPatternEnabled(false); 289 saveLockPattern(null); 290 setLong(PASSWORD_TYPE_KEY, MODE_PATTERN); 291 } 292 293 /** 294 * Save a lock pattern. 295 * @param pattern The new pattern to save. 296 */ 297 public void saveLockPattern(List<LockPatternView.Cell> pattern) { 298 // Compute the hash 299 final byte[] hash = LockPatternUtils.patternToHash(pattern); 300 try { 301 // Write the hash to file 302 RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "rw"); 303 // Truncate the file if pattern is null, to clear the lock 304 if (pattern == null) { 305 raf.setLength(0); 306 } else { 307 raf.write(hash, 0, hash.length); 308 } 309 raf.close(); 310 if (pattern != null) { 311 setBoolean(PATTERN_EVER_CHOSEN_KEY, true); 312 setLong(PASSWORD_TYPE_KEY, MODE_PATTERN); 313 DevicePolicyManager dpm = (DevicePolicyManager)mContext.getSystemService( 314 Context.DEVICE_POLICY_SERVICE); 315 dpm.setActivePasswordState( 316 DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern.size()); 317 } 318 } catch (FileNotFoundException fnfe) { 319 // Cant do much, unless we want to fail over to using the settings provider 320 Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename); 321 } catch (IOException ioe) { 322 // Cant do much 323 Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename); 324 } 325 } 326 327 /** 328 * Compare the given password and mode, ensuring that the password meets 329 * the mode and returning the minimum mode needed for the given password. 330 * @param password The password to be used. 331 * @param reqMode The desired password mode. 332 * @return Returns {@link #MODE_UNSPECIFIED} if the password is not 333 * good enough for the given mode. Otherwise, returns either the original 334 * reqMode or something better if that is needed for the given password. 335 */ 336 static public int adjustPasswordMode(String password, int reqMode) { 337 boolean hasDigit = false; 338 boolean hasNonDigit = false; 339 final int len = password.length(); 340 for (int i = 0; i < len; i++) { 341 if (Character.isDigit(password.charAt(i))) { 342 hasDigit = true; 343 } else { 344 hasNonDigit = true; 345 } 346 } 347 348 // First check if it is sufficient. 349 switch (reqMode) { 350 case MODE_PASSWORD: { 351 if (!hasDigit || !hasNonDigit) { 352 return MODE_UNSPECIFIED; 353 } 354 } break; 355 356 case MODE_PIN: 357 case MODE_PATTERN: { 358 // Whatever we have is acceptable; we may need to promote the 359 // mode later. 360 } break; 361 362 default: 363 // If it isn't a mode we specifically know, then fail fast. 364 Log.w(TAG, "adjustPasswordMode: unknown mode " + reqMode); 365 return MODE_UNSPECIFIED; 366 } 367 368 // Do we need to promote? 369 if (hasNonDigit) { 370 if (reqMode < MODE_PASSWORD) { 371 reqMode = MODE_PASSWORD; 372 } 373 } 374 if (hasDigit) { 375 if (reqMode < MODE_PIN) { 376 reqMode = MODE_PIN; 377 } 378 } 379 380 return reqMode; 381 } 382 383 /** 384 * Save a lock password. Does not ensure that the pattern is as good 385 * as the requested mode, but will adjust the mode to be as good as the 386 * pattern. 387 * @param password The password to save 388 */ 389 public void saveLockPassword(String password, int mode) { 390 // Compute the hash 391 final byte[] hash = passwordToHash(password); 392 try { 393 // Write the hash to file 394 RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "rw"); 395 // Truncate the file if pattern is null, to clear the lock 396 if (password == null) { 397 raf.setLength(0); 398 } else { 399 raf.write(hash, 0, hash.length); 400 } 401 raf.close(); 402 if (password != null) { 403 int finalMode = adjustPasswordMode(password, mode); 404 if (mode < finalMode) { 405 mode = finalMode; 406 } 407 setLong(PASSWORD_TYPE_KEY, mode); 408 DevicePolicyManager dpm = (DevicePolicyManager)mContext.getSystemService( 409 Context.DEVICE_POLICY_SERVICE); 410 dpm.setActivePasswordState(mode, password.length()); 411 } 412 } catch (FileNotFoundException fnfe) { 413 // Cant do much, unless we want to fail over to using the settings provider 414 Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename); 415 } catch (IOException ioe) { 416 // Cant do much 417 Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename); 418 } 419 } 420 421 public int getPasswordMode() { 422 return (int) getLong(PASSWORD_TYPE_KEY, MODE_PATTERN); 423 } 424 425 /** 426 * Deserialize a pattern. 427 * @param string The pattern serialized with {@link #patternToString} 428 * @return The pattern. 429 */ 430 public static List<LockPatternView.Cell> stringToPattern(String string) { 431 List<LockPatternView.Cell> result = Lists.newArrayList(); 432 433 final byte[] bytes = string.getBytes(); 434 for (int i = 0; i < bytes.length; i++) { 435 byte b = bytes[i]; 436 result.add(LockPatternView.Cell.of(b / 3, b % 3)); 437 } 438 return result; 439 } 440 441 /** 442 * Serialize a pattern. 443 * @param pattern The pattern. 444 * @return The pattern in string form. 445 */ 446 public static String patternToString(List<LockPatternView.Cell> pattern) { 447 if (pattern == null) { 448 return ""; 449 } 450 final int patternSize = pattern.size(); 451 452 byte[] res = new byte[patternSize]; 453 for (int i = 0; i < patternSize; i++) { 454 LockPatternView.Cell cell = pattern.get(i); 455 res[i] = (byte) (cell.getRow() * 3 + cell.getColumn()); 456 } 457 return new String(res); 458 } 459 460 /* 461 * Generate an SHA-1 hash for the pattern. Not the most secure, but it is 462 * at least a second level of protection. First level is that the file 463 * is in a location only readable by the system process. 464 * @param pattern the gesture pattern. 465 * @return the hash of the pattern in a byte array. 466 */ 467 private static byte[] patternToHash(List<LockPatternView.Cell> pattern) { 468 if (pattern == null) { 469 return null; 470 } 471 472 final int patternSize = pattern.size(); 473 byte[] res = new byte[patternSize]; 474 for (int i = 0; i < patternSize; i++) { 475 LockPatternView.Cell cell = pattern.get(i); 476 res[i] = (byte) (cell.getRow() * 3 + cell.getColumn()); 477 } 478 try { 479 MessageDigest md = MessageDigest.getInstance("SHA-1"); 480 byte[] hash = md.digest(res); 481 return hash; 482 } catch (NoSuchAlgorithmException nsa) { 483 return res; 484 } 485 } 486 487 private String getSalt() { 488 long salt = getLong(LOCK_PASSWORD_SALT_KEY, 0); 489 if (salt == 0) { 490 try { 491 salt = SecureRandom.getInstance("SHA1PRNG").nextLong(); 492 setLong(LOCK_PASSWORD_SALT_KEY, salt); 493 Log.v(TAG, "Initialized lock password salt"); 494 } catch (NoSuchAlgorithmException e) { 495 // Throw an exception rather than storing a password we'll never be able to recover 496 throw new IllegalStateException("Couldn't get SecureRandom number", e); 497 } 498 } 499 return Long.toHexString(salt); 500 } 501 502 /* 503 * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash. 504 * Not the most secure, but it is at least a second level of protection. First level is that 505 * the file is in a location only readable by the system process. 506 * @param password the gesture pattern. 507 * @return the hash of the pattern in a byte array. 508 */ 509 public byte[] passwordToHash(String password) { 510 if (password == null) { 511 return null; 512 } 513 String algo = null; 514 byte[] hashed = null; 515 try { 516 byte[] saltedPassword = (password + getSalt()).getBytes(); 517 byte[] sha1 = MessageDigest.getInstance(algo = "SHA-1").digest(saltedPassword); 518 byte[] md5 = MessageDigest.getInstance(algo = "MD5").digest(saltedPassword); 519 hashed = (toHex(sha1) + toHex(md5)).getBytes(); 520 } catch (NoSuchAlgorithmException e) { 521 Log.w(TAG, "Failed to encode string because of missing algorithm: " + algo); 522 } 523 return hashed; 524 } 525 526 private static String toHex(byte[] ary) { 527 final String hex = "0123456789ABCDEF"; 528 String ret = ""; 529 for (int i = 0; i < ary.length; i++) { 530 ret += hex.charAt((ary[i] >> 4) & 0xf); 531 ret += hex.charAt(ary[i] & 0xf); 532 } 533 return ret; 534 } 535 536 /** 537 * @return Whether the lock password is enabled. 538 */ 539 public boolean isLockPasswordEnabled() { 540 long mode = getLong(PASSWORD_TYPE_KEY, 0); 541 return savedPasswordExists() && (mode == MODE_PASSWORD || mode == MODE_PIN); 542 } 543 544 /** 545 * @return Whether the lock pattern is enabled. 546 */ 547 public boolean isLockPatternEnabled() { 548 return getBoolean(Settings.System.LOCK_PATTERN_ENABLED) 549 && getLong(PASSWORD_TYPE_KEY, MODE_PATTERN) == MODE_PATTERN; 550 } 551 552 /** 553 * Set whether the lock pattern is enabled. 554 */ 555 public void setLockPatternEnabled(boolean enabled) { 556 setBoolean(Settings.System.LOCK_PATTERN_ENABLED, enabled); 557 } 558 559 /** 560 * @return Whether the visible pattern is enabled. 561 */ 562 public boolean isVisiblePatternEnabled() { 563 return getBoolean(Settings.System.LOCK_PATTERN_VISIBLE); 564 } 565 566 /** 567 * Set whether the visible pattern is enabled. 568 */ 569 public void setVisiblePatternEnabled(boolean enabled) { 570 setBoolean(Settings.System.LOCK_PATTERN_VISIBLE, enabled); 571 } 572 573 /** 574 * @return Whether tactile feedback for the pattern is enabled. 575 */ 576 public boolean isTactileFeedbackEnabled() { 577 return getBoolean(Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED); 578 } 579 580 /** 581 * Set whether tactile feedback for the pattern is enabled. 582 */ 583 public void setTactileFeedbackEnabled(boolean enabled) { 584 setBoolean(Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED, enabled); 585 } 586 587 /** 588 * Set and store the lockout deadline, meaning the user can't attempt his/her unlock 589 * pattern until the deadline has passed. 590 * @return the chosen deadline. 591 */ 592 public long setLockoutAttemptDeadline() { 593 final long deadline = SystemClock.elapsedRealtime() + FAILED_ATTEMPT_TIMEOUT_MS; 594 setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline); 595 return deadline; 596 } 597 598 /** 599 * @return The elapsed time in millis in the future when the user is allowed to 600 * attempt to enter his/her lock pattern, or 0 if the user is welcome to 601 * enter a pattern. 602 */ 603 public long getLockoutAttemptDeadline() { 604 final long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L); 605 final long now = SystemClock.elapsedRealtime(); 606 if (deadline < now || deadline > (now + FAILED_ATTEMPT_TIMEOUT_MS)) { 607 return 0L; 608 } 609 return deadline; 610 } 611 612 /** 613 * @return Whether the user is permanently locked out until they verify their 614 * credentials. Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed 615 * attempts. 616 */ 617 public boolean isPermanentlyLocked() { 618 return getBoolean(LOCKOUT_PERMANENT_KEY); 619 } 620 621 /** 622 * Set the state of whether the device is permanently locked, meaning the user 623 * must authenticate via other means. 624 * 625 * @param locked Whether the user is permanently locked out until they verify their 626 * credentials. Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed 627 * attempts. 628 */ 629 public void setPermanentlyLocked(boolean locked) { 630 setBoolean(LOCKOUT_PERMANENT_KEY, locked); 631 } 632 633 /** 634 * @return A formatted string of the next alarm (for showing on the lock screen), 635 * or null if there is no next alarm. 636 */ 637 public String getNextAlarm() { 638 String nextAlarm = Settings.System.getString(mContentResolver, 639 Settings.System.NEXT_ALARM_FORMATTED); 640 if (nextAlarm == null || TextUtils.isEmpty(nextAlarm)) { 641 return null; 642 } 643 return nextAlarm; 644 } 645 646 private boolean getBoolean(String systemSettingKey) { 647 // STOPSHIP: these need to be moved to secure settings! 648 return 1 == 649 android.provider.Settings.System.getInt( 650 mContentResolver, 651 systemSettingKey, 0); 652 } 653 654 private void setBoolean(String systemSettingKey, boolean enabled) { 655 // STOPSHIP: these need to be moved to secure settings! 656 android.provider.Settings.System.putInt( 657 mContentResolver, 658 systemSettingKey, 659 enabled ? 1 : 0); 660 } 661 662 private long getLong(String systemSettingKey, long def) { 663 // STOPSHIP: these need to be moved to secure settings! 664 return android.provider.Settings.System.getLong(mContentResolver, systemSettingKey, def); 665 } 666 667 private void setLong(String systemSettingKey, long value) { 668 // STOPSHIP: these need to be moved to secure settings! 669 android.provider.Settings.System.putLong(mContentResolver, systemSettingKey, value); 670 } 671 672 public boolean isSecure() { 673 long mode = getPasswordMode(); 674 boolean secure = mode == MODE_PATTERN && isLockPatternEnabled() && savedPatternExists() 675 || (mode == MODE_PIN || mode == MODE_PASSWORD) && savedPasswordExists(); 676 return secure; 677 } 678} 679