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