blob: ab91a732f3ccf177089bde2e7b2b2b56fc09900f [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;
Ricky Waidc283a82016-03-24 19:55:08 +000032import android.util.SparseArray;
Adrian Roos261d5ab2014-10-29 14:42:38 +010033
34import java.io.File;
35import java.io.IOException;
36import java.io.RandomAccessFile;
37
38import static android.content.Context.USER_SERVICE;
39
40/**
41 * Storage for the lock settings service.
42 */
43class LockSettingsStorage {
44
45 private static final String TAG = "LockSettingsStorage";
46 private static final String TABLE = "locksettings";
Ricky Waidc283a82016-03-24 19:55:08 +000047 private static final boolean DEBUG = false;
Adrian Roos261d5ab2014-10-29 14:42:38 +010048
49 private static final String COLUMN_KEY = "name";
50 private static final String COLUMN_USERID = "user";
51 private static final String COLUMN_VALUE = "value";
52
53 private static final String[] COLUMNS_FOR_QUERY = {
54 COLUMN_VALUE
55 };
Adrian Roos3dcae682014-10-29 14:43:56 +010056 private static final String[] COLUMNS_FOR_PREFETCH = {
57 COLUMN_KEY, COLUMN_VALUE
58 };
Adrian Roos261d5ab2014-10-29 14:42:38 +010059
60 private static final String SYSTEM_DIRECTORY = "/system/";
Andres Moralese40bad82015-05-28 14:21:36 -070061 private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key";
62 private static final String BASE_ZERO_LOCK_PATTERN_FILE = "gatekeeper.gesture.key";
Andres Morales8fa56652015-03-31 09:19:50 -070063 private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key";
64 private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
65 private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
Ricky Waidc283a82016-03-24 19:55:08 +000066 private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
Adrian Roos261d5ab2014-10-29 14:42:38 +010067
Adrian Roos3dcae682014-10-29 14:43:56 +010068 private static final Object DEFAULT = new Object();
69
Adrian Roos261d5ab2014-10-29 14:42:38 +010070 private final DatabaseHelper mOpenHelper;
71 private final Context mContext;
Adrian Roos3dcae682014-10-29 14:43:56 +010072 private final Cache mCache = new Cache();
Adrian Roos261d5ab2014-10-29 14:42:38 +010073 private final Object mFileWriteLock = new Object();
74
Ricky Waidc283a82016-03-24 19:55:08 +000075 private SparseArray<Integer> mStoredCredentialType;
Andres Morales8fa56652015-03-31 09:19:50 -070076
Paul Crowleycc701552016-05-17 14:18:49 -070077 static class CredentialHash {
Andres Morales8fa56652015-03-31 09:19:50 -070078 static final int TYPE_NONE = -1;
79 static final int TYPE_PATTERN = 1;
80 static final int TYPE_PASSWORD = 2;
81
82 static final int VERSION_LEGACY = 0;
83 static final int VERSION_GATEKEEPER = 1;
84
85 CredentialHash(byte[] hash, int version) {
86 this.hash = hash;
87 this.version = version;
Andres Moralese40bad82015-05-28 14:21:36 -070088 this.isBaseZeroPattern = false;
89 }
90
91 CredentialHash(byte[] hash, boolean isBaseZeroPattern) {
92 this.hash = hash;
93 this.version = VERSION_GATEKEEPER;
94 this.isBaseZeroPattern = isBaseZeroPattern;
Andres Morales8fa56652015-03-31 09:19:50 -070095 }
96
97 byte[] hash;
98 int version;
Andres Moralese40bad82015-05-28 14:21:36 -070099 boolean isBaseZeroPattern;
Andres Morales8fa56652015-03-31 09:19:50 -0700100 }
101
Adrian Roos3dcae682014-10-29 14:43:56 +0100102 public LockSettingsStorage(Context context, Callback callback) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100103 mContext = context;
104 mOpenHelper = new DatabaseHelper(context, callback);
Ricky Waidc283a82016-03-24 19:55:08 +0000105 mStoredCredentialType = new SparseArray<Integer>();
Adrian Roos261d5ab2014-10-29 14:42:38 +0100106 }
107
Adrian Roos3dcae682014-10-29 14:43:56 +0100108 public void writeKeyValue(String key, String value, int userId) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100109 writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
110 }
111
Adrian Roos3dcae682014-10-29 14:43:56 +0100112 public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100113 ContentValues cv = new ContentValues();
114 cv.put(COLUMN_KEY, key);
115 cv.put(COLUMN_USERID, userId);
116 cv.put(COLUMN_VALUE, value);
117
118 db.beginTransaction();
119 try {
120 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
121 new String[] {key, Integer.toString(userId)});
122 db.insert(TABLE, null, cv);
123 db.setTransactionSuccessful();
Adrian Roos3dcae682014-10-29 14:43:56 +0100124 mCache.putKeyValue(key, value, userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100125 } finally {
126 db.endTransaction();
127 }
128
129 }
130
Adrian Roos3dcae682014-10-29 14:43:56 +0100131 public String readKeyValue(String key, String defaultValue, int userId) {
132 int version;
133 synchronized (mCache) {
134 if (mCache.hasKeyValue(key, userId)) {
135 return mCache.peekKeyValue(key, defaultValue, userId);
136 }
137 version = mCache.getVersion();
138 }
139
Adrian Roos261d5ab2014-10-29 14:42:38 +0100140 Cursor cursor;
Adrian Roos3dcae682014-10-29 14:43:56 +0100141 Object result = DEFAULT;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100142 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
143 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
144 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
145 new String[] { Integer.toString(userId), key },
146 null, null, null)) != null) {
147 if (cursor.moveToFirst()) {
148 result = cursor.getString(0);
149 }
150 cursor.close();
151 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100152 mCache.putKeyValueIfUnchanged(key, result, userId, version);
153 return result == DEFAULT ? defaultValue : (String) result;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100154 }
155
Adrian Roos3dcae682014-10-29 14:43:56 +0100156 public void prefetchUser(int userId) {
157 int version;
158 synchronized (mCache) {
159 if (mCache.isFetched(userId)) {
160 return;
161 }
162 mCache.setFetched(userId);
163 version = mCache.getVersion();
164 }
165
166 Cursor cursor;
167 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
168 if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH,
169 COLUMN_USERID + "=?",
170 new String[] { Integer.toString(userId) },
171 null, null, null)) != null) {
172 while (cursor.moveToNext()) {
173 String key = cursor.getString(0);
174 String value = cursor.getString(1);
175 mCache.putKeyValueIfUnchanged(key, value, userId, version);
176 }
177 cursor.close();
178 }
179
180 // Populate cache by reading the password and pattern files.
181 readPasswordHash(userId);
182 readPatternHash(userId);
183 }
184
Andres Morales8fa56652015-03-31 09:19:50 -0700185 public int getStoredCredentialType(int userId) {
Ricky Waidc283a82016-03-24 19:55:08 +0000186 final Integer cachedStoredCredentialType = mStoredCredentialType.get(userId);
187 if (cachedStoredCredentialType != null) {
188 return cachedStoredCredentialType.intValue();
Adrian Roos261d5ab2014-10-29 14:42:38 +0100189 }
Andres Morales8fa56652015-03-31 09:19:50 -0700190
Ricky Waidc283a82016-03-24 19:55:08 +0000191 int storedCredentialType;
Andres Morales8fa56652015-03-31 09:19:50 -0700192 CredentialHash pattern = readPatternHash(userId);
193 if (pattern == null) {
194 if (readPasswordHash(userId) != null) {
Ricky Waidc283a82016-03-24 19:55:08 +0000195 storedCredentialType = CredentialHash.TYPE_PASSWORD;
Andres Morales8fa56652015-03-31 09:19:50 -0700196 } else {
Ricky Waidc283a82016-03-24 19:55:08 +0000197 storedCredentialType = CredentialHash.TYPE_NONE;
Andres Morales8fa56652015-03-31 09:19:50 -0700198 }
199 } else {
200 CredentialHash password = readPasswordHash(userId);
201 if (password != null) {
202 // Both will never be GateKeeper
203 if (password.version == CredentialHash.VERSION_GATEKEEPER) {
Ricky Waidc283a82016-03-24 19:55:08 +0000204 storedCredentialType = CredentialHash.TYPE_PASSWORD;
Andres Morales8fa56652015-03-31 09:19:50 -0700205 } else {
Ricky Waidc283a82016-03-24 19:55:08 +0000206 storedCredentialType = CredentialHash.TYPE_PATTERN;
Andres Morales8fa56652015-03-31 09:19:50 -0700207 }
208 } else {
Ricky Waidc283a82016-03-24 19:55:08 +0000209 storedCredentialType = CredentialHash.TYPE_PATTERN;
Andres Morales8fa56652015-03-31 09:19:50 -0700210 }
211 }
Ricky Waidc283a82016-03-24 19:55:08 +0000212 mStoredCredentialType.put(userId, storedCredentialType);
213 return storedCredentialType;
Andres Morales8fa56652015-03-31 09:19:50 -0700214 }
215
216
217 public CredentialHash readPasswordHash(int userId) {
218 byte[] stored = readFile(getLockPasswordFilename(userId));
219 if (stored != null && stored.length > 0) {
220 return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER);
221 }
222
223 stored = readFile(getLegacyLockPasswordFilename(userId));
224 if (stored != null && stored.length > 0) {
225 return new CredentialHash(stored, CredentialHash.VERSION_LEGACY);
226 }
227
Adrian Roos261d5ab2014-10-29 14:42:38 +0100228 return null;
229 }
230
Andres Morales8fa56652015-03-31 09:19:50 -0700231 public CredentialHash readPatternHash(int userId) {
232 byte[] stored = readFile(getLockPatternFilename(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100233 if (stored != null && stored.length > 0) {
Andres Morales8fa56652015-03-31 09:19:50 -0700234 return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100235 }
Andres Morales8fa56652015-03-31 09:19:50 -0700236
Andres Moralese40bad82015-05-28 14:21:36 -0700237 stored = readFile(getBaseZeroLockPatternFilename(userId));
238 if (stored != null && stored.length > 0) {
239 return new CredentialHash(stored, true);
240 }
241
Andres Morales8fa56652015-03-31 09:19:50 -0700242 stored = readFile(getLegacyLockPatternFilename(userId));
243 if (stored != null && stored.length > 0) {
244 return new CredentialHash(stored, CredentialHash.VERSION_LEGACY);
245 }
246
Adrian Roos261d5ab2014-10-29 14:42:38 +0100247 return null;
248 }
249
Ricky Waidc283a82016-03-24 19:55:08 +0000250 public void removeChildProfileLock(int userId) {
251 if (DEBUG)
252 Slog.e(TAG, "Remove child profile lock for user: " + userId);
253 try {
254 deleteFile(getChildProfileLockFile(userId));
255 } catch (Exception e) {
256 e.printStackTrace();
257 }
258 }
259
260 public void writeChildProfileLock(int userId, byte[] lock) {
261 writeFile(getChildProfileLockFile(userId), lock);
262 }
263
264 public byte[] readChildProfileLock(int userId) {
265 return readFile(getChildProfileLockFile(userId));
266 }
267
268 public boolean hasChildProfileLock(int userId) {
269 return hasFile(getChildProfileLockFile(userId));
270 }
Andres Moralese40bad82015-05-28 14:21:36 -0700271
Adrian Roos3dcae682014-10-29 14:43:56 +0100272 public boolean hasPassword(int userId) {
Andres Morales8fa56652015-03-31 09:19:50 -0700273 return hasFile(getLockPasswordFilename(userId)) ||
274 hasFile(getLegacyLockPasswordFilename(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100275 }
276
Adrian Roos3dcae682014-10-29 14:43:56 +0100277 public boolean hasPattern(int userId) {
Andres Morales8fa56652015-03-31 09:19:50 -0700278 return hasFile(getLockPatternFilename(userId)) ||
Andres Moralese40bad82015-05-28 14:21:36 -0700279 hasFile(getBaseZeroLockPatternFilename(userId)) ||
Andres Morales8fa56652015-03-31 09:19:50 -0700280 hasFile(getLegacyLockPatternFilename(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100281 }
282
Adrian Roos3dcae682014-10-29 14:43:56 +0100283 private boolean hasFile(String name) {
284 byte[] contents = readFile(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100285 return contents != null && contents.length > 0;
286 }
287
Adrian Roos3dcae682014-10-29 14:43:56 +0100288 private byte[] readFile(String name) {
289 int version;
290 synchronized (mCache) {
291 if (mCache.hasFile(name)) {
292 return mCache.peekFile(name);
293 }
294 version = mCache.getVersion();
295 }
296
Adrian Roos261d5ab2014-10-29 14:42:38 +0100297 RandomAccessFile raf = null;
298 byte[] stored = null;
299 try {
300 raf = new RandomAccessFile(name, "r");
301 stored = new byte[(int) raf.length()];
302 raf.readFully(stored, 0, stored.length);
303 raf.close();
304 } catch (IOException e) {
305 Slog.e(TAG, "Cannot read file " + e);
306 } finally {
307 if (raf != null) {
308 try {
309 raf.close();
310 } catch (IOException e) {
311 Slog.e(TAG, "Error closing file " + e);
312 }
313 }
314 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100315 mCache.putFileIfUnchanged(name, stored, version);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100316 return stored;
317 }
318
Adrian Roos3dcae682014-10-29 14:43:56 +0100319 private void writeFile(String name, byte[] hash) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100320 synchronized (mFileWriteLock) {
321 RandomAccessFile raf = null;
322 try {
323 // Write the hash to file
324 raf = new RandomAccessFile(name, "rw");
325 // Truncate the file if pattern is null, to clear the lock
326 if (hash == null || hash.length == 0) {
327 raf.setLength(0);
328 } else {
329 raf.write(hash, 0, hash.length);
330 }
331 raf.close();
332 } catch (IOException e) {
333 Slog.e(TAG, "Error writing to file " + e);
334 } finally {
335 if (raf != null) {
336 try {
337 raf.close();
338 } catch (IOException e) {
339 Slog.e(TAG, "Error closing file " + e);
340 }
341 }
342 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100343 mCache.putFile(name, hash);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100344 }
345 }
346
Andres Moralese40bad82015-05-28 14:21:36 -0700347 private void deleteFile(String name) {
Ricky Waidc283a82016-03-24 19:55:08 +0000348 if (DEBUG) Slog.e(TAG, "Delete file " + name);
349 synchronized (mFileWriteLock) {
350 File file = new File(name);
351 if (file.exists()) {
352 file.delete();
353 mCache.putFile(name, null);
354 }
Andres Moralese40bad82015-05-28 14:21:36 -0700355 }
356 }
357
Adrian Roos261d5ab2014-10-29 14:42:38 +0100358 public void writePatternHash(byte[] hash, int userId) {
Ricky Waidc283a82016-03-24 19:55:08 +0000359 mStoredCredentialType.put(userId, hash == null ? CredentialHash.TYPE_NONE
360 : CredentialHash.TYPE_PATTERN);
Adrian Roos3dcae682014-10-29 14:43:56 +0100361 writeFile(getLockPatternFilename(userId), hash);
Robin Lee68e4ba42015-03-10 12:34:28 +0000362 clearPasswordHash(userId);
363 }
364
365 private void clearPatternHash(int userId) {
366 writeFile(getLockPatternFilename(userId), null);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100367 }
368
369 public void writePasswordHash(byte[] hash, int userId) {
Ricky Waidc283a82016-03-24 19:55:08 +0000370 mStoredCredentialType.put(userId, hash == null ? CredentialHash.TYPE_NONE
371 : CredentialHash.TYPE_PASSWORD);
Adrian Roos3dcae682014-10-29 14:43:56 +0100372 writeFile(getLockPasswordFilename(userId), hash);
Robin Lee68e4ba42015-03-10 12:34:28 +0000373 clearPatternHash(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100374 }
375
Robin Lee68e4ba42015-03-10 12:34:28 +0000376 private void clearPasswordHash(int userId) {
377 writeFile(getLockPasswordFilename(userId), null);
378 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100379
Adrian Roose5424992014-11-07 21:47:17 +0100380 @VisibleForTesting
381 String getLockPatternFilename(int userId) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100382 return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100383 }
384
Adrian Roose5424992014-11-07 21:47:17 +0100385 @VisibleForTesting
386 String getLockPasswordFilename(int userId) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100387 return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
388 }
389
Andres Morales8fa56652015-03-31 09:19:50 -0700390 @VisibleForTesting
391 String getLegacyLockPatternFilename(int userId) {
392 return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE);
393 }
394
395 @VisibleForTesting
396 String getLegacyLockPasswordFilename(int userId) {
397 return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE);
398 }
399
Andres Moralese40bad82015-05-28 14:21:36 -0700400 private String getBaseZeroLockPatternFilename(int userId) {
401 return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE);
402 }
403
Ricky Waia46b40f2016-03-31 16:48:29 +0100404 @VisibleForTesting
405 String getChildProfileLockFile(int userId) {
Ricky Waidc283a82016-03-24 19:55:08 +0000406 return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
407 }
408
Adrian Roos3dcae682014-10-29 14:43:56 +0100409 private String getLockCredentialFilePathForUser(int userId, String basename) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100410 String dataSystemDirectory =
411 android.os.Environment.getDataDirectory().getAbsolutePath() +
412 SYSTEM_DIRECTORY;
413 if (userId == 0) {
414 // Leave it in the same place for user 0
Adrian Roos3dcae682014-10-29 14:43:56 +0100415 return dataSystemDirectory + basename;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100416 } else {
Adrian Roos3dcae682014-10-29 14:43:56 +0100417 return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
Adrian Roos261d5ab2014-10-29 14:42:38 +0100418 }
419 }
420
Adrian Roos261d5ab2014-10-29 14:42:38 +0100421 public void removeUser(int userId) {
422 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
423
424 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
425 final UserInfo parentInfo = um.getProfileParent(userId);
426
Robin Lee68e4ba42015-03-10 12:34:28 +0000427 if (parentInfo == null) {
428 // This user owns its lock settings files - safe to delete them
429 synchronized (mFileWriteLock) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100430 String name = getLockPasswordFilename(userId);
431 File file = new File(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100432 if (file.exists()) {
433 file.delete();
Adrian Roos3dcae682014-10-29 14:43:56 +0100434 mCache.putFile(name, null);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100435 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100436 name = getLockPatternFilename(userId);
437 file = new File(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100438 if (file.exists()) {
439 file.delete();
Adrian Roos3dcae682014-10-29 14:43:56 +0100440 mCache.putFile(name, null);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100441 }
442 }
Ricky Waidc283a82016-03-24 19:55:08 +0000443 } else {
444 // Manged profile
445 removeChildProfileLock(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100446 }
447
448 try {
449 db.beginTransaction();
450 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
451 db.setTransactionSuccessful();
Adrian Roos3dcae682014-10-29 14:43:56 +0100452 mCache.removeUser(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100453 } finally {
454 db.endTransaction();
455 }
456 }
457
Adrian Roose5424992014-11-07 21:47:17 +0100458 @VisibleForTesting
459 void closeDatabase() {
460 mOpenHelper.close();
461 }
462
463 @VisibleForTesting
464 void clearCache() {
465 mCache.clear();
466 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100467
Adrian Roos3dcae682014-10-29 14:43:56 +0100468 public interface Callback {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100469 void initialize(SQLiteDatabase db);
470 }
471
472 class DatabaseHelper extends SQLiteOpenHelper {
473 private static final String TAG = "LockSettingsDB";
474 private static final String DATABASE_NAME = "locksettings.db";
475
476 private static final int DATABASE_VERSION = 2;
477
478 private final Callback mCallback;
479
480 public DatabaseHelper(Context context, Callback callback) {
481 super(context, DATABASE_NAME, null, DATABASE_VERSION);
482 setWriteAheadLoggingEnabled(true);
483 mCallback = callback;
484 }
485
486 private void createTable(SQLiteDatabase db) {
487 db.execSQL("CREATE TABLE " + TABLE + " (" +
488 "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
489 COLUMN_KEY + " TEXT," +
490 COLUMN_USERID + " INTEGER," +
491 COLUMN_VALUE + " TEXT" +
492 ");");
493 }
494
495 @Override
496 public void onCreate(SQLiteDatabase db) {
497 createTable(db);
498 mCallback.initialize(db);
499 }
500
501 @Override
502 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
503 int upgradeVersion = oldVersion;
504 if (upgradeVersion == 1) {
505 // Previously migrated lock screen widget settings. Now defunct.
506 upgradeVersion = 2;
507 }
508
509 if (upgradeVersion != DATABASE_VERSION) {
510 Log.w(TAG, "Failed to upgrade database!");
511 }
512 }
513 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100514
515 /**
516 * Cache consistency model:
517 * - Writes to storage write directly to the cache, but this MUST happen within the atomic
518 * section either provided by the database transaction or mWriteLock, such that writes to the
519 * cache and writes to the backing storage are guaranteed to occur in the same order
520 *
521 * - Reads can populate the cache, but because they are no strong ordering guarantees with
522 * respect to writes this precaution is taken:
523 * - The cache is assigned a version number that increases every time the cache is modified.
524 * Reads from backing storage can only populate the cache if the backing storage
525 * has not changed since the load operation has begun.
526 * This guarantees that no read operation can shadow a write to the cache that happens
527 * after it had begun.
528 */
529 private static class Cache {
530 private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
531 private final CacheKey mCacheKey = new CacheKey();
532 private int mVersion = 0;
533
534 String peekKeyValue(String key, String defaultValue, int userId) {
535 Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
536 return cached == DEFAULT ? defaultValue : (String) cached;
537 }
538
539 boolean hasKeyValue(String key, int userId) {
540 return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
541 }
542
543 void putKeyValue(String key, String value, int userId) {
544 put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
545 }
546
547 void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
548 putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
549 }
550
551 byte[] peekFile(String fileName) {
552 return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
553 }
554
555 boolean hasFile(String fileName) {
556 return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
557 }
558
559 void putFile(String key, byte[] value) {
560 put(CacheKey.TYPE_FILE, key, value, -1 /* userId */);
561 }
562
563 void putFileIfUnchanged(String key, byte[] value, int version) {
564 putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version);
565 }
566
567 void setFetched(int userId) {
568 put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
569 }
570
571 boolean isFetched(int userId) {
572 return contains(CacheKey.TYPE_FETCHED, "", userId);
573 }
574
575
576 private synchronized void put(int type, String key, Object value, int userId) {
577 // Create a new CachKey here because it may be saved in the map if the key is absent.
578 mCache.put(new CacheKey().set(type, key, userId), value);
579 mVersion++;
580 }
581
582 private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
583 int version) {
584 if (!contains(type, key, userId) && mVersion == version) {
585 put(type, key, value, userId);
586 }
587 }
588
589 private synchronized boolean contains(int type, String key, int userId) {
590 return mCache.containsKey(mCacheKey.set(type, key, userId));
591 }
592
593 private synchronized Object peek(int type, String key, int userId) {
594 return mCache.get(mCacheKey.set(type, key, userId));
595 }
596
597 private synchronized int getVersion() {
598 return mVersion;
599 }
600
601 synchronized void removeUser(int userId) {
602 for (int i = mCache.size() - 1; i >= 0; i--) {
603 if (mCache.keyAt(i).userId == userId) {
604 mCache.removeAt(i);
605 }
606 }
607
608 // Make sure in-flight loads can't write to cache.
609 mVersion++;
610 }
611
Adrian Roose5424992014-11-07 21:47:17 +0100612 synchronized void clear() {
613 mCache.clear();
614 mVersion++;
615 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100616
617 private static final class CacheKey {
618 static final int TYPE_KEY_VALUE = 0;
619 static final int TYPE_FILE = 1;
620 static final int TYPE_FETCHED = 2;
621
622 String key;
623 int userId;
624 int type;
625
626 public CacheKey set(int type, String key, int userId) {
627 this.type = type;
628 this.key = key;
629 this.userId = userId;
630 return this;
631 }
632
633 @Override
634 public boolean equals(Object obj) {
635 if (!(obj instanceof CacheKey))
636 return false;
637 CacheKey o = (CacheKey) obj;
638 return userId == o.userId && type == o.type && key.equals(o.key);
639 }
640
641 @Override
642 public int hashCode() {
643 return key.hashCode() ^ userId ^ type;
644 }
645 }
646 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100647}