blob: d81daa904de5181a4cea6db5b432985f0359f7ab [file] [log] [blame]
Adrian Roos261d5ab2014-10-29 14:42:38 +01001/*
2 * Copyright (C) 2014 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.server;
18
Adrian Roose5424992014-11-07 21:47:17 +010019import com.android.internal.annotations.VisibleForTesting;
20
Adrian Roos261d5ab2014-10-29 14:42:38 +010021import android.content.ContentValues;
22import android.content.Context;
23import android.content.pm.UserInfo;
24import android.database.Cursor;
25import android.database.sqlite.SQLiteDatabase;
26import android.database.sqlite.SQLiteOpenHelper;
27import android.os.Environment;
28import android.os.UserManager;
29import android.util.ArrayMap;
30import android.util.Log;
31import android.util.Slog;
32
33import java.io.File;
34import java.io.IOException;
35import java.io.RandomAccessFile;
36
37import static android.content.Context.USER_SERVICE;
38
39/**
40 * Storage for the lock settings service.
41 */
42class LockSettingsStorage {
43
44 private static final String TAG = "LockSettingsStorage";
45 private static final String TABLE = "locksettings";
46
47 private static final String COLUMN_KEY = "name";
48 private static final String COLUMN_USERID = "user";
49 private static final String COLUMN_VALUE = "value";
50
51 private static final String[] COLUMNS_FOR_QUERY = {
52 COLUMN_VALUE
53 };
Adrian Roos3dcae682014-10-29 14:43:56 +010054 private static final String[] COLUMNS_FOR_PREFETCH = {
55 COLUMN_KEY, COLUMN_VALUE
56 };
Adrian Roos261d5ab2014-10-29 14:42:38 +010057
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
Adrian Roos3dcae682014-10-29 14:43:56 +010062 private static final Object DEFAULT = new Object();
63
Adrian Roos261d5ab2014-10-29 14:42:38 +010064 private final DatabaseHelper mOpenHelper;
65 private final Context mContext;
Adrian Roos3dcae682014-10-29 14:43:56 +010066 private final Cache mCache = new Cache();
Adrian Roos261d5ab2014-10-29 14:42:38 +010067 private final Object mFileWriteLock = new Object();
68
Adrian Roos3dcae682014-10-29 14:43:56 +010069 public LockSettingsStorage(Context context, Callback callback) {
Adrian Roos261d5ab2014-10-29 14:42:38 +010070 mContext = context;
71 mOpenHelper = new DatabaseHelper(context, callback);
72 }
73
Adrian Roos3dcae682014-10-29 14:43:56 +010074 public void writeKeyValue(String key, String value, int userId) {
Adrian Roos261d5ab2014-10-29 14:42:38 +010075 writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
76 }
77
Adrian Roos3dcae682014-10-29 14:43:56 +010078 public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
Adrian Roos261d5ab2014-10-29 14:42:38 +010079 ContentValues cv = new ContentValues();
80 cv.put(COLUMN_KEY, key);
81 cv.put(COLUMN_USERID, userId);
82 cv.put(COLUMN_VALUE, value);
83
84 db.beginTransaction();
85 try {
86 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
87 new String[] {key, Integer.toString(userId)});
88 db.insert(TABLE, null, cv);
89 db.setTransactionSuccessful();
Adrian Roos3dcae682014-10-29 14:43:56 +010090 mCache.putKeyValue(key, value, userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +010091 } finally {
92 db.endTransaction();
93 }
94
95 }
96
Adrian Roos3dcae682014-10-29 14:43:56 +010097 public String readKeyValue(String key, String defaultValue, int userId) {
98 int version;
99 synchronized (mCache) {
100 if (mCache.hasKeyValue(key, userId)) {
101 return mCache.peekKeyValue(key, defaultValue, userId);
102 }
103 version = mCache.getVersion();
104 }
105
Adrian Roos261d5ab2014-10-29 14:42:38 +0100106 Cursor cursor;
Adrian Roos3dcae682014-10-29 14:43:56 +0100107 Object result = DEFAULT;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100108 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
109 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
110 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
111 new String[] { Integer.toString(userId), key },
112 null, null, null)) != null) {
113 if (cursor.moveToFirst()) {
114 result = cursor.getString(0);
115 }
116 cursor.close();
117 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100118 mCache.putKeyValueIfUnchanged(key, result, userId, version);
119 return result == DEFAULT ? defaultValue : (String) result;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100120 }
121
Adrian Roos3dcae682014-10-29 14:43:56 +0100122 public void prefetchUser(int userId) {
123 int version;
124 synchronized (mCache) {
125 if (mCache.isFetched(userId)) {
126 return;
127 }
128 mCache.setFetched(userId);
129 version = mCache.getVersion();
130 }
131
132 Cursor cursor;
133 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
134 if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH,
135 COLUMN_USERID + "=?",
136 new String[] { Integer.toString(userId) },
137 null, null, null)) != null) {
138 while (cursor.moveToNext()) {
139 String key = cursor.getString(0);
140 String value = cursor.getString(1);
141 mCache.putKeyValueIfUnchanged(key, value, userId, version);
142 }
143 cursor.close();
144 }
145
146 // Populate cache by reading the password and pattern files.
147 readPasswordHash(userId);
148 readPatternHash(userId);
149 }
150
151 public byte[] readPasswordHash(int userId) {
152 final byte[] stored = readFile(getLockPasswordFilename(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100153 if (stored != null && stored.length > 0) {
154 return stored;
155 }
156 return null;
157 }
158
Adrian Roos3dcae682014-10-29 14:43:56 +0100159 public byte[] readPatternHash(int userId) {
160 final byte[] stored = readFile(getLockPatternFilename(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100161 if (stored != null && stored.length > 0) {
162 return stored;
163 }
164 return null;
165 }
166
Adrian Roos3dcae682014-10-29 14:43:56 +0100167 public boolean hasPassword(int userId) {
168 return hasFile(getLockPasswordFilename(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100169 }
170
Adrian Roos3dcae682014-10-29 14:43:56 +0100171 public boolean hasPattern(int userId) {
172 return hasFile(getLockPatternFilename(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100173 }
174
Adrian Roos3dcae682014-10-29 14:43:56 +0100175 private boolean hasFile(String name) {
176 byte[] contents = readFile(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100177 return contents != null && contents.length > 0;
178 }
179
Adrian Roos3dcae682014-10-29 14:43:56 +0100180 private byte[] readFile(String name) {
181 int version;
182 synchronized (mCache) {
183 if (mCache.hasFile(name)) {
184 return mCache.peekFile(name);
185 }
186 version = mCache.getVersion();
187 }
188
Adrian Roos261d5ab2014-10-29 14:42:38 +0100189 RandomAccessFile raf = null;
190 byte[] stored = null;
191 try {
192 raf = new RandomAccessFile(name, "r");
193 stored = new byte[(int) raf.length()];
194 raf.readFully(stored, 0, stored.length);
195 raf.close();
196 } catch (IOException e) {
197 Slog.e(TAG, "Cannot read file " + e);
198 } finally {
199 if (raf != null) {
200 try {
201 raf.close();
202 } catch (IOException e) {
203 Slog.e(TAG, "Error closing file " + e);
204 }
205 }
206 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100207 mCache.putFileIfUnchanged(name, stored, version);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100208 return stored;
209 }
210
Adrian Roos3dcae682014-10-29 14:43:56 +0100211 private void writeFile(String name, byte[] hash) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100212 synchronized (mFileWriteLock) {
213 RandomAccessFile raf = null;
214 try {
215 // Write the hash to file
216 raf = new RandomAccessFile(name, "rw");
217 // Truncate the file if pattern is null, to clear the lock
218 if (hash == null || hash.length == 0) {
219 raf.setLength(0);
220 } else {
221 raf.write(hash, 0, hash.length);
222 }
223 raf.close();
224 } catch (IOException e) {
225 Slog.e(TAG, "Error writing to file " + e);
226 } finally {
227 if (raf != null) {
228 try {
229 raf.close();
230 } catch (IOException e) {
231 Slog.e(TAG, "Error closing file " + e);
232 }
233 }
234 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100235 mCache.putFile(name, hash);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100236 }
237 }
238
239 public void writePatternHash(byte[] hash, int userId) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100240 writeFile(getLockPatternFilename(userId), hash);
Robin Lee68e4ba42015-03-10 12:34:28 +0000241 clearPasswordHash(userId);
242 }
243
244 private void clearPatternHash(int userId) {
245 writeFile(getLockPatternFilename(userId), null);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100246 }
247
248 public void writePasswordHash(byte[] hash, int userId) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100249 writeFile(getLockPasswordFilename(userId), hash);
Robin Lee68e4ba42015-03-10 12:34:28 +0000250 clearPatternHash(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100251 }
252
Robin Lee68e4ba42015-03-10 12:34:28 +0000253 private void clearPasswordHash(int userId) {
254 writeFile(getLockPasswordFilename(userId), null);
255 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100256
Adrian Roose5424992014-11-07 21:47:17 +0100257 @VisibleForTesting
258 String getLockPatternFilename(int userId) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100259 return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100260 }
261
Adrian Roose5424992014-11-07 21:47:17 +0100262 @VisibleForTesting
263 String getLockPasswordFilename(int userId) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100264 return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
265 }
266
267 private String getLockCredentialFilePathForUser(int userId, String basename) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100268 userId = getUserParentOrSelfId(userId);
269 String dataSystemDirectory =
270 android.os.Environment.getDataDirectory().getAbsolutePath() +
271 SYSTEM_DIRECTORY;
272 if (userId == 0) {
273 // Leave it in the same place for user 0
Adrian Roos3dcae682014-10-29 14:43:56 +0100274 return dataSystemDirectory + basename;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100275 } else {
Adrian Roos3dcae682014-10-29 14:43:56 +0100276 return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
Adrian Roos261d5ab2014-10-29 14:42:38 +0100277 }
278 }
279
280 private int getUserParentOrSelfId(int userId) {
281 if (userId != 0) {
282 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
283 final UserInfo pi = um.getProfileParent(userId);
284 if (pi != null) {
285 return pi.id;
286 }
287 }
288 return userId;
289 }
290
Adrian Roos261d5ab2014-10-29 14:42:38 +0100291 public void removeUser(int userId) {
292 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
293
294 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
295 final UserInfo parentInfo = um.getProfileParent(userId);
296
Robin Lee68e4ba42015-03-10 12:34:28 +0000297 if (parentInfo == null) {
298 // This user owns its lock settings files - safe to delete them
299 synchronized (mFileWriteLock) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100300 String name = getLockPasswordFilename(userId);
301 File file = new File(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100302 if (file.exists()) {
303 file.delete();
Adrian Roos3dcae682014-10-29 14:43:56 +0100304 mCache.putFile(name, null);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100305 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100306 name = getLockPatternFilename(userId);
307 file = new File(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100308 if (file.exists()) {
309 file.delete();
Adrian Roos3dcae682014-10-29 14:43:56 +0100310 mCache.putFile(name, null);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100311 }
312 }
313 }
314
315 try {
316 db.beginTransaction();
317 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
318 db.setTransactionSuccessful();
Adrian Roos3dcae682014-10-29 14:43:56 +0100319 mCache.removeUser(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100320 } finally {
321 db.endTransaction();
322 }
323 }
324
Adrian Roose5424992014-11-07 21:47:17 +0100325 @VisibleForTesting
326 void closeDatabase() {
327 mOpenHelper.close();
328 }
329
330 @VisibleForTesting
331 void clearCache() {
332 mCache.clear();
333 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100334
Adrian Roos3dcae682014-10-29 14:43:56 +0100335 public interface Callback {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100336 void initialize(SQLiteDatabase db);
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 = 2;
344
345 private final Callback mCallback;
346
347 public DatabaseHelper(Context context, Callback callback) {
348 super(context, DATABASE_NAME, null, DATABASE_VERSION);
349 setWriteAheadLoggingEnabled(true);
350 mCallback = callback;
351 }
352
353 private void createTable(SQLiteDatabase db) {
354 db.execSQL("CREATE TABLE " + TABLE + " (" +
355 "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
356 COLUMN_KEY + " TEXT," +
357 COLUMN_USERID + " INTEGER," +
358 COLUMN_VALUE + " TEXT" +
359 ");");
360 }
361
362 @Override
363 public void onCreate(SQLiteDatabase db) {
364 createTable(db);
365 mCallback.initialize(db);
366 }
367
368 @Override
369 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
370 int upgradeVersion = oldVersion;
371 if (upgradeVersion == 1) {
372 // Previously migrated lock screen widget settings. Now defunct.
373 upgradeVersion = 2;
374 }
375
376 if (upgradeVersion != DATABASE_VERSION) {
377 Log.w(TAG, "Failed to upgrade database!");
378 }
379 }
380 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100381
382 /**
383 * Cache consistency model:
384 * - Writes to storage write directly to the cache, but this MUST happen within the atomic
385 * section either provided by the database transaction or mWriteLock, such that writes to the
386 * cache and writes to the backing storage are guaranteed to occur in the same order
387 *
388 * - Reads can populate the cache, but because they are no strong ordering guarantees with
389 * respect to writes this precaution is taken:
390 * - The cache is assigned a version number that increases every time the cache is modified.
391 * Reads from backing storage can only populate the cache if the backing storage
392 * has not changed since the load operation has begun.
393 * This guarantees that no read operation can shadow a write to the cache that happens
394 * after it had begun.
395 */
396 private static class Cache {
397 private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
398 private final CacheKey mCacheKey = new CacheKey();
399 private int mVersion = 0;
400
401 String peekKeyValue(String key, String defaultValue, int userId) {
402 Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
403 return cached == DEFAULT ? defaultValue : (String) cached;
404 }
405
406 boolean hasKeyValue(String key, int userId) {
407 return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
408 }
409
410 void putKeyValue(String key, String value, int userId) {
411 put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
412 }
413
414 void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
415 putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
416 }
417
418 byte[] peekFile(String fileName) {
419 return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
420 }
421
422 boolean hasFile(String fileName) {
423 return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
424 }
425
426 void putFile(String key, byte[] value) {
427 put(CacheKey.TYPE_FILE, key, value, -1 /* userId */);
428 }
429
430 void putFileIfUnchanged(String key, byte[] value, int version) {
431 putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version);
432 }
433
434 void setFetched(int userId) {
435 put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
436 }
437
438 boolean isFetched(int userId) {
439 return contains(CacheKey.TYPE_FETCHED, "", userId);
440 }
441
442
443 private synchronized void put(int type, String key, Object value, int userId) {
444 // Create a new CachKey here because it may be saved in the map if the key is absent.
445 mCache.put(new CacheKey().set(type, key, userId), value);
446 mVersion++;
447 }
448
449 private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
450 int version) {
451 if (!contains(type, key, userId) && mVersion == version) {
452 put(type, key, value, userId);
453 }
454 }
455
456 private synchronized boolean contains(int type, String key, int userId) {
457 return mCache.containsKey(mCacheKey.set(type, key, userId));
458 }
459
460 private synchronized Object peek(int type, String key, int userId) {
461 return mCache.get(mCacheKey.set(type, key, userId));
462 }
463
464 private synchronized int getVersion() {
465 return mVersion;
466 }
467
468 synchronized void removeUser(int userId) {
469 for (int i = mCache.size() - 1; i >= 0; i--) {
470 if (mCache.keyAt(i).userId == userId) {
471 mCache.removeAt(i);
472 }
473 }
474
475 // Make sure in-flight loads can't write to cache.
476 mVersion++;
477 }
478
Adrian Roose5424992014-11-07 21:47:17 +0100479 synchronized void clear() {
480 mCache.clear();
481 mVersion++;
482 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100483
484 private static final class CacheKey {
485 static final int TYPE_KEY_VALUE = 0;
486 static final int TYPE_FILE = 1;
487 static final int TYPE_FETCHED = 2;
488
489 String key;
490 int userId;
491 int type;
492
493 public CacheKey set(int type, String key, int userId) {
494 this.type = type;
495 this.key = key;
496 this.userId = userId;
497 return this;
498 }
499
500 @Override
501 public boolean equals(Object obj) {
502 if (!(obj instanceof CacheKey))
503 return false;
504 CacheKey o = (CacheKey) obj;
505 return userId == o.userId && type == o.type && key.equals(o.key);
506 }
507
508 @Override
509 public int hashCode() {
510 return key.hashCode() ^ userId ^ type;
511 }
512 }
513 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100514}