• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.internal.widget;
18 
19 import android.content.ContentResolver;
20 import android.os.SystemClock;
21 import android.provider.Settings;
22 import android.security.MessageDigest;
23 import android.text.TextUtils;
24 import android.util.Log;
25 
26 import com.google.android.collect.Lists;
27 
28 import java.io.FileNotFoundException;
29 import java.io.IOException;
30 import java.io.RandomAccessFile;
31 import java.security.NoSuchAlgorithmException;
32 import java.util.Arrays;
33 import java.util.List;
34 
35 /**
36  * Utilities for the lock patten and its settings.
37  */
38 public class LockPatternUtils {
39 
40     private static final String TAG = "LockPatternUtils";
41 
42     private static final String LOCK_PATTERN_FILE = "/system/gesture.key";
43 
44     /**
45      * The maximum number of incorrect attempts before the user is prevented
46      * from trying again for {@link #FAILED_ATTEMPT_TIMEOUT_MS}.
47      */
48     public static final int FAILED_ATTEMPTS_BEFORE_TIMEOUT = 5;
49 
50     /**
51      * The number of incorrect attempts before which we fall back on an alternative
52      * method of verifying the user, and resetting their lock pattern.
53      */
54     public static final int FAILED_ATTEMPTS_BEFORE_RESET = 20;
55 
56     /**
57      * How long the user is prevented from trying again after entering the
58      * wrong pattern too many times.
59      */
60     public static final long FAILED_ATTEMPT_TIMEOUT_MS = 30000L;
61 
62     /**
63      * The interval of the countdown for showing progress of the lockout.
64      */
65     public static final long FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS = 1000L;
66 
67     /**
68      * The minimum number of dots in a valid pattern.
69      */
70     public static final int MIN_LOCK_PATTERN_SIZE = 4;
71 
72     /**
73      * The minimum number of dots the user must include in a wrong pattern
74      * attempt for it to be counted against the counts that affect
75      * {@link #FAILED_ATTEMPTS_BEFORE_TIMEOUT} and {@link #FAILED_ATTEMPTS_BEFORE_RESET}
76      */
77     public static final int MIN_PATTERN_REGISTER_FAIL = 3;
78 
79     private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
80     private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
81     private final static String PATTERN_EVER_CHOSEN = "lockscreen.patterneverchosen";
82 
83     private final ContentResolver mContentResolver;
84 
85     private static String sLockPatternFilename;
86 
87     /**
88      * @param contentResolver Used to look up and save settings.
89      */
LockPatternUtils(ContentResolver contentResolver)90     public LockPatternUtils(ContentResolver contentResolver) {
91         mContentResolver = contentResolver;
92         // Initialize the location of gesture lock file
93         if (sLockPatternFilename == null) {
94             sLockPatternFilename = android.os.Environment.getDataDirectory()
95                     .getAbsolutePath() + LOCK_PATTERN_FILE;
96         }
97     }
98 
99     /**
100      * Check to see if a pattern matches the saved pattern.  If no pattern exists,
101      * always returns true.
102      * @param pattern The pattern to check.
103      * @return Whether the pattern matchees the stored one.
104      */
checkPattern(List<LockPatternView.Cell> pattern)105     public boolean checkPattern(List<LockPatternView.Cell> pattern) {
106         try {
107             // Read all the bytes from the file
108             RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "r");
109             final byte[] stored = new byte[(int) raf.length()];
110             int got = raf.read(stored, 0, stored.length);
111             raf.close();
112             if (got <= 0) {
113                 return true;
114             }
115             // Compare the hash from the file with the entered pattern's hash
116             return Arrays.equals(stored, LockPatternUtils.patternToHash(pattern));
117         } catch (FileNotFoundException fnfe) {
118             return true;
119         } catch (IOException ioe) {
120             return true;
121         }
122     }
123 
124     /**
125      * Check to see if the user has stored a lock pattern.
126      * @return Whether a saved pattern exists.
127      */
savedPatternExists()128     public boolean savedPatternExists() {
129         try {
130             // Check if we can read a byte from the file
131             RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "r");
132             byte first = raf.readByte();
133             raf.close();
134             return true;
135         } catch (FileNotFoundException fnfe) {
136             return false;
137         } catch (IOException ioe) {
138             return false;
139         }
140     }
141 
142     /**
143      * Return true if the user has ever chosen a pattern.  This is true even if the pattern is
144      * currently cleared.
145      *
146      * @return True if the user has ever chosen a pattern.
147      */
isPatternEverChosen()148     public boolean isPatternEverChosen() {
149         return getBoolean(PATTERN_EVER_CHOSEN);
150     }
151 
152     /**
153      * Save a lock pattern.
154      * @param pattern The new pattern to save.
155      */
saveLockPattern(List<LockPatternView.Cell> pattern)156     public void saveLockPattern(List<LockPatternView.Cell> pattern) {
157         // Compute the hash
158         final byte[] hash  = LockPatternUtils.patternToHash(pattern);
159         try {
160             // Write the hash to file
161             RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "rw");
162             // Truncate the file if pattern is null, to clear the lock
163             if (pattern == null) {
164                 raf.setLength(0);
165             } else {
166                 raf.write(hash, 0, hash.length);
167             }
168             raf.close();
169             setBoolean(PATTERN_EVER_CHOSEN, true);
170         } catch (FileNotFoundException fnfe) {
171             // Cant do much, unless we want to fail over to using the settings provider
172             Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
173         } catch (IOException ioe) {
174             // Cant do much
175             Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
176         }
177     }
178 
179     /**
180      * Deserialize a pattern.
181      * @param string The pattern serialized with {@link #patternToString}
182      * @return The pattern.
183      */
stringToPattern(String string)184     public static List<LockPatternView.Cell> stringToPattern(String string) {
185         List<LockPatternView.Cell> result = Lists.newArrayList();
186 
187         final byte[] bytes = string.getBytes();
188         for (int i = 0; i < bytes.length; i++) {
189             byte b = bytes[i];
190             result.add(LockPatternView.Cell.of(b / 3, b % 3));
191         }
192         return result;
193     }
194 
195     /**
196      * Serialize a pattern.
197      * @param pattern The pattern.
198      * @return The pattern in string form.
199      */
patternToString(List<LockPatternView.Cell> pattern)200     public static String patternToString(List<LockPatternView.Cell> pattern) {
201         if (pattern == null) {
202             return "";
203         }
204         final int patternSize = pattern.size();
205 
206         byte[] res = new byte[patternSize];
207         for (int i = 0; i < patternSize; i++) {
208             LockPatternView.Cell cell = pattern.get(i);
209             res[i] = (byte) (cell.getRow() * 3 + cell.getColumn());
210         }
211         return new String(res);
212     }
213 
214     /*
215      * Generate an SHA-1 hash for the pattern. Not the most secure, but it is
216      * at least a second level of protection. First level is that the file
217      * is in a location only readable by the system process.
218      * @param pattern the gesture pattern.
219      * @return the hash of the pattern in a byte array.
220      */
patternToHash(List<LockPatternView.Cell> pattern)221     static byte[] patternToHash(List<LockPatternView.Cell> pattern) {
222         if (pattern == null) {
223             return null;
224         }
225 
226         final int patternSize = pattern.size();
227         byte[] res = new byte[patternSize];
228         for (int i = 0; i < patternSize; i++) {
229             LockPatternView.Cell cell = pattern.get(i);
230             res[i] = (byte) (cell.getRow() * 3 + cell.getColumn());
231         }
232         try {
233             MessageDigest md = MessageDigest.getInstance("SHA-1");
234             byte[] hash = md.digest(res);
235             return hash;
236         } catch (NoSuchAlgorithmException nsa) {
237             return res;
238         }
239     }
240 
241     /**
242      * @return Whether the lock pattern is enabled.
243      */
isLockPatternEnabled()244     public boolean isLockPatternEnabled() {
245         return getBoolean(Settings.System.LOCK_PATTERN_ENABLED);
246     }
247 
248     /**
249      * Set whether the lock pattern is enabled.
250      */
setLockPatternEnabled(boolean enabled)251     public void setLockPatternEnabled(boolean enabled) {
252         setBoolean(Settings.System.LOCK_PATTERN_ENABLED, enabled);
253     }
254 
255     /**
256      * @return Whether the visible pattern is enabled.
257      */
isVisiblePatternEnabled()258     public boolean isVisiblePatternEnabled() {
259         return getBoolean(Settings.System.LOCK_PATTERN_VISIBLE);
260     }
261 
262     /**
263      * Set whether the visible pattern is enabled.
264      */
setVisiblePatternEnabled(boolean enabled)265     public void setVisiblePatternEnabled(boolean enabled) {
266         setBoolean(Settings.System.LOCK_PATTERN_VISIBLE, enabled);
267     }
268 
269     /**
270      * @return Whether tactile feedback for the pattern is enabled.
271      */
isTactileFeedbackEnabled()272     public boolean isTactileFeedbackEnabled() {
273         return getBoolean(Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED);
274     }
275 
276     /**
277      * Set whether tactile feedback for the pattern is enabled.
278      */
setTactileFeedbackEnabled(boolean enabled)279     public void setTactileFeedbackEnabled(boolean enabled) {
280         setBoolean(Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED, enabled);
281     }
282 
283     /**
284      * Set and store the lockout deadline, meaning the user can't attempt his/her unlock
285      * pattern until the deadline has passed.
286      * @return the chosen deadline.
287      */
setLockoutAttemptDeadline()288     public long setLockoutAttemptDeadline() {
289         final long deadline = SystemClock.elapsedRealtime() + FAILED_ATTEMPT_TIMEOUT_MS;
290         setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline);
291         return deadline;
292     }
293 
294     /**
295      * @return The elapsed time in millis in the future when the user is allowed to
296      *   attempt to enter his/her lock pattern, or 0 if the user is welcome to
297      *   enter a pattern.
298      */
getLockoutAttemptDeadline()299     public long getLockoutAttemptDeadline() {
300         final long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L);
301         final long now = SystemClock.elapsedRealtime();
302         if (deadline < now || deadline > (now + FAILED_ATTEMPT_TIMEOUT_MS)) {
303             return 0L;
304         }
305         return deadline;
306     }
307 
308     /**
309      * @return Whether the user is permanently locked out until they verify their
310      *   credentials.  Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed
311      *   attempts.
312      */
isPermanentlyLocked()313     public boolean isPermanentlyLocked() {
314         return getBoolean(LOCKOUT_PERMANENT_KEY);
315     }
316 
317     /**
318      * Set the state of whether the device is permanently locked, meaning the user
319      * must authenticate via other means.
320      *
321      * @param locked Whether the user is permanently locked out until they verify their
322      *   credentials.  Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed
323      *   attempts.
324      */
setPermanentlyLocked(boolean locked)325     public void setPermanentlyLocked(boolean locked) {
326         setBoolean(LOCKOUT_PERMANENT_KEY, locked);
327     }
328 
329     /**
330      * @return A formatted string of the next alarm (for showing on the lock screen),
331      *   or null if there is no next alarm.
332      */
getNextAlarm()333     public String getNextAlarm() {
334         String nextAlarm = Settings.System.getString(mContentResolver,
335                 Settings.System.NEXT_ALARM_FORMATTED);
336         if (nextAlarm == null || TextUtils.isEmpty(nextAlarm)) {
337             return null;
338         }
339         return nextAlarm;
340     }
341 
getBoolean(String systemSettingKey)342     private boolean getBoolean(String systemSettingKey) {
343         return 1 ==
344                 android.provider.Settings.System.getInt(
345                         mContentResolver,
346                         systemSettingKey, 0);
347     }
348 
setBoolean(String systemSettingKey, boolean enabled)349     private void setBoolean(String systemSettingKey, boolean enabled) {
350         android.provider.Settings.System.putInt(
351                         mContentResolver,
352                         systemSettingKey,
353                         enabled ? 1 : 0);
354     }
355 
getLong(String systemSettingKey, long def)356     private long getLong(String systemSettingKey, long def) {
357         return android.provider.Settings.System.getLong(mContentResolver, systemSettingKey, def);
358     }
359 
setLong(String systemSettingKey, long value)360     private void setLong(String systemSettingKey, long value) {
361         android.provider.Settings.System.putLong(mContentResolver, systemSettingKey, value);
362     }
363 
364 
365 }
366