blob: 24c7161a1e147986fe0ac7e0da407cdc38c71565 [file] [log] [blame]
Amith Yamasani52c489c2012-03-28 11:42:42 -07001/*
2 * Copyright (C) 2012 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.content.ContentResolver;
20import android.content.ContentValues;
21import android.content.Context;
22import android.database.Cursor;
23import android.database.sqlite.SQLiteDatabase;
24import android.database.sqlite.SQLiteOpenHelper;
25import android.os.Binder;
26import android.os.RemoteException;
27import android.os.UserId;
28import android.provider.Settings;
29import android.provider.Settings.Secure;
30import android.text.TextUtils;
31import android.util.Slog;
32
33import java.io.File;
34import java.io.FileNotFoundException;
35import java.io.IOException;
36import java.io.RandomAccessFile;
37import java.util.Arrays;
38
39/**
40 * Keeps the lock pattern/password data and related settings for each user.
41 * Used by LockPatternUtils. Needs to be a service because Settings app also needs
42 * to be able to save lockscreen information for secondary users.
43 * @hide
44 */
45public class LockSettingsService extends ILockSettings.Stub {
46
47 private final DatabaseHelper mOpenHelper;
48 private static final String TAG = "LockSettingsService";
49
50 private static final String TABLE = "locksettings";
51 private static final String COLUMN_KEY = "name";
52 private static final String COLUMN_USERID = "user";
53 private static final String COLUMN_VALUE = "value";
54
55 private static final String[] COLUMNS_FOR_QUERY = {
56 COLUMN_VALUE
57 };
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 private final Context mContext;
64
65 public LockSettingsService(Context context) {
66 mContext = context;
67 // Open the database
68 mOpenHelper = new DatabaseHelper(mContext);
69 }
70
71 public void systemReady() {
72 migrateOldData();
73 }
74
75 private void migrateOldData() {
76 try {
77 if (getString("migrated", null, 0) != null) {
78 // Already migrated
79 return;
80 }
81
82 final ContentResolver cr = mContext.getContentResolver();
83 for (String validSetting : VALID_SETTINGS) {
84 String value = Settings.Secure.getString(cr, validSetting);
85 if (value != null) {
86 setString(validSetting, value, 0);
87 }
88 }
89 // No need to move the password / pattern files. They're already in the right place.
90 setString("migrated", "true", 0);
91 Slog.i(TAG, "Migrated lock settings to new location");
92 } catch (RemoteException re) {
93 Slog.e(TAG, "Unable to migrate old data");
94 }
95 }
96
97 private static final void checkWritePermission(int userId) {
98 final int callingUid = Binder.getCallingUid();
99 if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
100 throw new SecurityException("uid=" + callingUid
101 + " not authorized to write lock settings");
102 }
103 }
104
105 private static final void checkPasswordReadPermission(int userId) {
106 final int callingUid = Binder.getCallingUid();
107 if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
108 throw new SecurityException("uid=" + callingUid
109 + " not authorized to read lock password");
110 }
111 }
112
113 private static final void checkReadPermission(int userId) {
114 final int callingUid = Binder.getCallingUid();
115 if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID
116 && UserId.getUserId(callingUid) != userId) {
117 throw new SecurityException("uid=" + callingUid
118 + " not authorized to read settings of user " + userId);
119 }
120 }
121
122 @Override
123 public void setBoolean(String key, boolean value, int userId) throws RemoteException {
124 checkWritePermission(userId);
125
126 writeToDb(key, value ? "1" : "0", userId);
127 }
128
129 @Override
130 public void setLong(String key, long value, int userId) throws RemoteException {
131 checkWritePermission(userId);
132
133 writeToDb(key, Long.toString(value), userId);
134 }
135
136 @Override
137 public void setString(String key, String value, int userId) throws RemoteException {
138 checkWritePermission(userId);
139
140 writeToDb(key, value, userId);
141 }
142
143 @Override
144 public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
145 //checkReadPermission(userId);
146
147 String value = readFromDb(key, null, userId);
148 return TextUtils.isEmpty(value) ?
149 defaultValue : (value.equals("1") || value.equals("true"));
150 }
151
152 @Override
153 public long getLong(String key, long defaultValue, int userId) throws RemoteException {
154 //checkReadPermission(userId);
155
156 String value = readFromDb(key, null, userId);
157 return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
158 }
159
160 @Override
161 public String getString(String key, String defaultValue, int userId) throws RemoteException {
162 //checkReadPermission(userId);
163
164 return readFromDb(key, defaultValue, userId);
165 }
166
167 private String getLockPatternFilename(int userId) {
168 String dataSystemDirectory =
169 android.os.Environment.getDataDirectory().getAbsolutePath() +
170 SYSTEM_DIRECTORY;
171 if (userId == 0) {
172 // Leave it in the same place for user 0
173 return dataSystemDirectory + LOCK_PATTERN_FILE;
174 } else {
175 return dataSystemDirectory + "users/" + userId + "/" + LOCK_PATTERN_FILE;
176 }
177 }
178
179 private String getLockPasswordFilename(int userId) {
180 String dataSystemDirectory =
181 android.os.Environment.getDataDirectory().getAbsolutePath() +
182 SYSTEM_DIRECTORY;
183 if (userId == 0) {
184 // Leave it in the same place for user 0
185 return dataSystemDirectory + LOCK_PASSWORD_FILE;
186 } else {
187 return dataSystemDirectory + "users/" + userId + "/" + LOCK_PASSWORD_FILE;
188 }
189 }
190
191 @Override
192 public boolean havePassword(int userId) throws RemoteException {
193 // Do we need a permissions check here?
194
195 return new File(getLockPasswordFilename(userId)).length() > 0;
196 }
197
198 @Override
199 public boolean havePattern(int userId) throws RemoteException {
200 // Do we need a permissions check here?
201
202 return new File(getLockPatternFilename(userId)).length() > 0;
203 }
204
205 @Override
206 public void setLockPattern(byte[] hash, int userId) throws RemoteException {
207 checkWritePermission(userId);
208
209 writeFile(getLockPatternFilename(userId), hash);
210 }
211
212 @Override
213 public boolean checkPattern(byte[] hash, int userId) throws RemoteException {
214 checkPasswordReadPermission(userId);
215 try {
216 // Read all the bytes from the file
217 RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r");
218 final byte[] stored = new byte[(int) raf.length()];
219 int got = raf.read(stored, 0, stored.length);
220 raf.close();
221 if (got <= 0) {
222 return true;
223 }
224 // Compare the hash from the file with the entered pattern's hash
225 return Arrays.equals(stored, hash);
226 } catch (FileNotFoundException fnfe) {
227 Slog.e(TAG, "Cannot read file " + fnfe);
228 return true;
229 } catch (IOException ioe) {
230 Slog.e(TAG, "Cannot read file " + ioe);
231 return true;
232 }
233 }
234
235 @Override
236 public void setLockPassword(byte[] hash, int userId) throws RemoteException {
237 checkWritePermission(userId);
238
239 writeFile(getLockPasswordFilename(userId), hash);
240 }
241
242 @Override
243 public boolean checkPassword(byte[] hash, int userId) throws RemoteException {
244 checkPasswordReadPermission(userId);
245
246 try {
247 // Read all the bytes from the file
248 RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "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, hash);
257 } catch (FileNotFoundException fnfe) {
258 Slog.e(TAG, "Cannot read file " + fnfe);
259 return true;
260 } catch (IOException ioe) {
261 Slog.e(TAG, "Cannot read file " + ioe);
262 return true;
263 }
264 }
265
266 @Override
267 public void removeUser(int userId) {
268 checkWritePermission(userId);
269
270 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
271 try {
272 File file = new File(getLockPasswordFilename(userId));
273 if (file.exists()) {
274 file.delete();
275 }
276 file = new File(getLockPatternFilename(userId));
277 if (file.exists()) {
278 file.delete();
279 }
280
281 db.beginTransaction();
282 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
283 db.setTransactionSuccessful();
284 } finally {
285 db.endTransaction();
286 }
287 }
288
289 private void writeFile(String name, byte[] hash) {
290 try {
291 // Write the hash to file
292 RandomAccessFile raf = new RandomAccessFile(name, "rw");
293 // Truncate the file if pattern is null, to clear the lock
294 if (hash == null || hash.length == 0) {
295 raf.setLength(0);
296 } else {
297 raf.write(hash, 0, hash.length);
298 }
299 raf.close();
300 } catch (IOException ioe) {
301 Slog.e(TAG, "Error writing to file " + ioe);
302 }
303 }
304
305 private void writeToDb(String key, String value, int userId) {
306 ContentValues cv = new ContentValues();
307 cv.put(COLUMN_KEY, key);
308 cv.put(COLUMN_USERID, userId);
309 cv.put(COLUMN_VALUE, value);
310
311 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
312 db.beginTransaction();
313 try {
314 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
315 new String[] {key, Integer.toString(userId)});
316 db.insert(TABLE, null, cv);
317 db.setTransactionSuccessful();
318 } finally {
319 db.endTransaction();
320 }
321 }
322
323 private String readFromDb(String key, String defaultValue, int userId) {
324 Cursor cursor;
325 String result = defaultValue;
326 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
327 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
328 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
329 new String[] { Integer.toString(userId), key },
330 null, null, null)) != null) {
331 if (cursor.moveToFirst()) {
332 result = cursor.getString(0);
333 }
334 cursor.close();
335 }
336 return result;
337 }
338
339 class DatabaseHelper extends SQLiteOpenHelper {
340 private static final String TAG = "LockSettingsDB";
341 private static final String DATABASE_NAME = "locksettings.db";
342
343 private static final int DATABASE_VERSION = 1;
344
345 public DatabaseHelper(Context context) {
346 super(context, DATABASE_NAME, null, DATABASE_VERSION);
347 setWriteAheadLoggingEnabled(true);
348 }
349
350 private void createTable(SQLiteDatabase db) {
351 db.execSQL("CREATE TABLE " + TABLE + " (" +
352 "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
353 COLUMN_KEY + " TEXT," +
354 COLUMN_USERID + " INTEGER," +
355 COLUMN_VALUE + " TEXT" +
356 ");");
357 }
358
359 @Override
360 public void onCreate(SQLiteDatabase db) {
361 createTable(db);
362 }
363
364 @Override
365 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
366 // Nothing yet
367 }
368 }
369
370 private static final String[] VALID_SETTINGS = new String[] {
371 LockPatternUtils.LOCKOUT_PERMANENT_KEY,
372 LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
373 LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
374 LockPatternUtils.PASSWORD_TYPE_KEY,
375 LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
376 LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
377 LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
378 LockPatternUtils.LOCKSCREEN_OPTIONS,
379 LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
380 LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
381 LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
382 LockPatternUtils.PASSWORD_HISTORY_KEY,
383 Secure.LOCK_PATTERN_ENABLED,
384 Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
385 Secure.LOCK_PATTERN_VISIBLE,
386 Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
387 };
388}