blob: d136f1af7654c217ec1c0d00f1701dfef231d2ef [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
77 class CredentialHash {
78 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 Waidc283a82016-03-24 19:55:08 +0000404 private String getChildProfileLockFile(int userId) {
405 return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
406 }
407
Adrian Roos3dcae682014-10-29 14:43:56 +0100408 private String getLockCredentialFilePathForUser(int userId, String basename) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100409 String dataSystemDirectory =
410 android.os.Environment.getDataDirectory().getAbsolutePath() +
411 SYSTEM_DIRECTORY;
412 if (userId == 0) {
413 // Leave it in the same place for user 0
Adrian Roos3dcae682014-10-29 14:43:56 +0100414 return dataSystemDirectory + basename;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100415 } else {
Adrian Roos3dcae682014-10-29 14:43:56 +0100416 return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
Adrian Roos261d5ab2014-10-29 14:42:38 +0100417 }
418 }
419
Adrian Roos261d5ab2014-10-29 14:42:38 +0100420 public void removeUser(int userId) {
421 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
422
423 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
424 final UserInfo parentInfo = um.getProfileParent(userId);
425
Robin Lee68e4ba42015-03-10 12:34:28 +0000426 if (parentInfo == null) {
427 // This user owns its lock settings files - safe to delete them
428 synchronized (mFileWriteLock) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100429 String name = getLockPasswordFilename(userId);
430 File file = new File(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100431 if (file.exists()) {
432 file.delete();
Adrian Roos3dcae682014-10-29 14:43:56 +0100433 mCache.putFile(name, null);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100434 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100435 name = getLockPatternFilename(userId);
436 file = new File(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100437 if (file.exists()) {
438 file.delete();
Adrian Roos3dcae682014-10-29 14:43:56 +0100439 mCache.putFile(name, null);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100440 }
441 }
Ricky Waidc283a82016-03-24 19:55:08 +0000442 } else {
443 // Manged profile
444 removeChildProfileLock(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100445 }
446
447 try {
448 db.beginTransaction();
449 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
450 db.setTransactionSuccessful();
Adrian Roos3dcae682014-10-29 14:43:56 +0100451 mCache.removeUser(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100452 } finally {
453 db.endTransaction();
454 }
455 }
456
Adrian Roose5424992014-11-07 21:47:17 +0100457 @VisibleForTesting
458 void closeDatabase() {
459 mOpenHelper.close();
460 }
461
462 @VisibleForTesting
463 void clearCache() {
464 mCache.clear();
465 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100466
Adrian Roos3dcae682014-10-29 14:43:56 +0100467 public interface Callback {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100468 void initialize(SQLiteDatabase db);
469 }
470
471 class DatabaseHelper extends SQLiteOpenHelper {
472 private static final String TAG = "LockSettingsDB";
473 private static final String DATABASE_NAME = "locksettings.db";
474
475 private static final int DATABASE_VERSION = 2;
476
477 private final Callback mCallback;
478
479 public DatabaseHelper(Context context, Callback callback) {
480 super(context, DATABASE_NAME, null, DATABASE_VERSION);
481 setWriteAheadLoggingEnabled(true);
482 mCallback = callback;
483 }
484
485 private void createTable(SQLiteDatabase db) {
486 db.execSQL("CREATE TABLE " + TABLE + " (" +
487 "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
488 COLUMN_KEY + " TEXT," +
489 COLUMN_USERID + " INTEGER," +
490 COLUMN_VALUE + " TEXT" +
491 ");");
492 }
493
494 @Override
495 public void onCreate(SQLiteDatabase db) {
496 createTable(db);
497 mCallback.initialize(db);
498 }
499
500 @Override
501 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
502 int upgradeVersion = oldVersion;
503 if (upgradeVersion == 1) {
504 // Previously migrated lock screen widget settings. Now defunct.
505 upgradeVersion = 2;
506 }
507
508 if (upgradeVersion != DATABASE_VERSION) {
509 Log.w(TAG, "Failed to upgrade database!");
510 }
511 }
512 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100513
514 /**
515 * Cache consistency model:
516 * - Writes to storage write directly to the cache, but this MUST happen within the atomic
517 * section either provided by the database transaction or mWriteLock, such that writes to the
518 * cache and writes to the backing storage are guaranteed to occur in the same order
519 *
520 * - Reads can populate the cache, but because they are no strong ordering guarantees with
521 * respect to writes this precaution is taken:
522 * - The cache is assigned a version number that increases every time the cache is modified.
523 * Reads from backing storage can only populate the cache if the backing storage
524 * has not changed since the load operation has begun.
525 * This guarantees that no read operation can shadow a write to the cache that happens
526 * after it had begun.
527 */
528 private static class Cache {
529 private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
530 private final CacheKey mCacheKey = new CacheKey();
531 private int mVersion = 0;
532
533 String peekKeyValue(String key, String defaultValue, int userId) {
534 Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
535 return cached == DEFAULT ? defaultValue : (String) cached;
536 }
537
538 boolean hasKeyValue(String key, int userId) {
539 return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
540 }
541
542 void putKeyValue(String key, String value, int userId) {
543 put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
544 }
545
546 void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
547 putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
548 }
549
550 byte[] peekFile(String fileName) {
551 return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
552 }
553
554 boolean hasFile(String fileName) {
555 return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
556 }
557
558 void putFile(String key, byte[] value) {
559 put(CacheKey.TYPE_FILE, key, value, -1 /* userId */);
560 }
561
562 void putFileIfUnchanged(String key, byte[] value, int version) {
563 putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version);
564 }
565
566 void setFetched(int userId) {
567 put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
568 }
569
570 boolean isFetched(int userId) {
571 return contains(CacheKey.TYPE_FETCHED, "", userId);
572 }
573
574
575 private synchronized void put(int type, String key, Object value, int userId) {
576 // Create a new CachKey here because it may be saved in the map if the key is absent.
577 mCache.put(new CacheKey().set(type, key, userId), value);
578 mVersion++;
579 }
580
581 private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
582 int version) {
583 if (!contains(type, key, userId) && mVersion == version) {
584 put(type, key, value, userId);
585 }
586 }
587
588 private synchronized boolean contains(int type, String key, int userId) {
589 return mCache.containsKey(mCacheKey.set(type, key, userId));
590 }
591
592 private synchronized Object peek(int type, String key, int userId) {
593 return mCache.get(mCacheKey.set(type, key, userId));
594 }
595
596 private synchronized int getVersion() {
597 return mVersion;
598 }
599
600 synchronized void removeUser(int userId) {
601 for (int i = mCache.size() - 1; i >= 0; i--) {
602 if (mCache.keyAt(i).userId == userId) {
603 mCache.removeAt(i);
604 }
605 }
606
607 // Make sure in-flight loads can't write to cache.
608 mVersion++;
609 }
610
Adrian Roose5424992014-11-07 21:47:17 +0100611 synchronized void clear() {
612 mCache.clear();
613 mVersion++;
614 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100615
616 private static final class CacheKey {
617 static final int TYPE_KEY_VALUE = 0;
618 static final int TYPE_FILE = 1;
619 static final int TYPE_FETCHED = 2;
620
621 String key;
622 int userId;
623 int type;
624
625 public CacheKey set(int type, String key, int userId) {
626 this.type = type;
627 this.key = key;
628 this.userId = userId;
629 return this;
630 }
631
632 @Override
633 public boolean equals(Object obj) {
634 if (!(obj instanceof CacheKey))
635 return false;
636 CacheKey o = (CacheKey) obj;
637 return userId == o.userId && type == o.type && key.equals(o.key);
638 }
639
640 @Override
641 public int hashCode() {
642 return key.hashCode() ^ userId ^ type;
643 }
644 }
645 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100646}