blob: 385b1cf4e44895420b229e7bf16ee8ae3fcba150 [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
Rubin Xu1de89b32016-11-30 20:03:13 +000019import static android.content.Context.USER_SERVICE;
Adrian Roose5424992014-11-07 21:47:17 +010020
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;
Rubin Xu1de89b32016-11-30 20:03:13 +000032
33import com.android.internal.annotations.VisibleForTesting;
34import com.android.internal.util.ArrayUtils;
35import com.android.internal.widget.LockPatternUtils;
Adrian Roos261d5ab2014-10-29 14:42:38 +010036
37import java.io.File;
38import java.io.IOException;
39import java.io.RandomAccessFile;
40
Adrian Roos261d5ab2014-10-29 14:42:38 +010041/**
42 * Storage for the lock settings service.
43 */
44class LockSettingsStorage {
45
46 private static final String TAG = "LockSettingsStorage";
47 private static final String TABLE = "locksettings";
Ricky Waidc283a82016-03-24 19:55:08 +000048 private static final boolean DEBUG = false;
Adrian Roos261d5ab2014-10-29 14:42:38 +010049
50 private static final String COLUMN_KEY = "name";
51 private static final String COLUMN_USERID = "user";
52 private static final String COLUMN_VALUE = "value";
53
54 private static final String[] COLUMNS_FOR_QUERY = {
55 COLUMN_VALUE
56 };
Adrian Roos3dcae682014-10-29 14:43:56 +010057 private static final String[] COLUMNS_FOR_PREFETCH = {
58 COLUMN_KEY, COLUMN_VALUE
59 };
Adrian Roos261d5ab2014-10-29 14:42:38 +010060
61 private static final String SYSTEM_DIRECTORY = "/system/";
Andres Moralese40bad82015-05-28 14:21:36 -070062 private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key";
63 private static final String BASE_ZERO_LOCK_PATTERN_FILE = "gatekeeper.gesture.key";
Andres Morales8fa56652015-03-31 09:19:50 -070064 private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key";
65 private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
66 private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
Ricky Waidc283a82016-03-24 19:55:08 +000067 private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
Adrian Roos261d5ab2014-10-29 14:42:38 +010068
Adrian Roos3dcae682014-10-29 14:43:56 +010069 private static final Object DEFAULT = new Object();
70
Adrian Roos261d5ab2014-10-29 14:42:38 +010071 private final DatabaseHelper mOpenHelper;
72 private final Context mContext;
Adrian Roos3dcae682014-10-29 14:43:56 +010073 private final Cache mCache = new Cache();
Adrian Roos261d5ab2014-10-29 14:42:38 +010074 private final Object mFileWriteLock = new Object();
75
Rubin Xu1de89b32016-11-30 20:03:13 +000076 @VisibleForTesting
77 public static class CredentialHash {
Andres Morales8fa56652015-03-31 09:19:50 -070078 static final int VERSION_LEGACY = 0;
79 static final int VERSION_GATEKEEPER = 1;
80
Rubin Xu1de89b32016-11-30 20:03:13 +000081 private CredentialHash(byte[] hash, int type, int version) {
82 if (type != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
83 if (hash == null) {
84 throw new RuntimeException("Empty hash for CredentialHash");
85 }
86 } else /* type == LockPatternUtils.CREDENTIAL_TYPE_NONE */ {
87 if (hash != null) {
88 throw new RuntimeException("None type CredentialHash should not have hash");
89 }
90 }
Andres Morales8fa56652015-03-31 09:19:50 -070091 this.hash = hash;
Rubin Xu1de89b32016-11-30 20:03:13 +000092 this.type = type;
Andres Morales8fa56652015-03-31 09:19:50 -070093 this.version = version;
Andres Moralese40bad82015-05-28 14:21:36 -070094 this.isBaseZeroPattern = false;
95 }
96
Rubin Xu1de89b32016-11-30 20:03:13 +000097 private CredentialHash(byte[] hash, boolean isBaseZeroPattern) {
Andres Moralese40bad82015-05-28 14:21:36 -070098 this.hash = hash;
Rubin Xu1de89b32016-11-30 20:03:13 +000099 this.type = LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
Andres Moralese40bad82015-05-28 14:21:36 -0700100 this.version = VERSION_GATEKEEPER;
101 this.isBaseZeroPattern = isBaseZeroPattern;
Andres Morales8fa56652015-03-31 09:19:50 -0700102 }
103
Rubin Xu1de89b32016-11-30 20:03:13 +0000104 static CredentialHash create(byte[] hash, int type) {
105 if (type == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
106 throw new RuntimeException("Bad type for CredentialHash");
107 }
108 return new CredentialHash(hash, type, VERSION_GATEKEEPER);
109 }
110
111 static CredentialHash createEmptyHash() {
112 return new CredentialHash(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
113 VERSION_GATEKEEPER);
114 }
115
Andres Morales8fa56652015-03-31 09:19:50 -0700116 byte[] hash;
Rubin Xu1de89b32016-11-30 20:03:13 +0000117 int type;
Andres Morales8fa56652015-03-31 09:19:50 -0700118 int version;
Andres Moralese40bad82015-05-28 14:21:36 -0700119 boolean isBaseZeroPattern;
Andres Morales8fa56652015-03-31 09:19:50 -0700120 }
121
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000122 public LockSettingsStorage(Context context) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100123 mContext = context;
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000124 mOpenHelper = new DatabaseHelper(context);
125 }
126
127 public void setDatabaseOnCreateCallback(Callback callback) {
128 mOpenHelper.setCallback(callback);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100129 }
130
Adrian Roos3dcae682014-10-29 14:43:56 +0100131 public void writeKeyValue(String key, String value, int userId) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100132 writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
133 }
134
Adrian Roos3dcae682014-10-29 14:43:56 +0100135 public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100136 ContentValues cv = new ContentValues();
137 cv.put(COLUMN_KEY, key);
138 cv.put(COLUMN_USERID, userId);
139 cv.put(COLUMN_VALUE, value);
140
141 db.beginTransaction();
142 try {
143 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
144 new String[] {key, Integer.toString(userId)});
145 db.insert(TABLE, null, cv);
146 db.setTransactionSuccessful();
Adrian Roos3dcae682014-10-29 14:43:56 +0100147 mCache.putKeyValue(key, value, userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100148 } finally {
149 db.endTransaction();
150 }
151
152 }
153
Adrian Roos3dcae682014-10-29 14:43:56 +0100154 public String readKeyValue(String key, String defaultValue, int userId) {
155 int version;
156 synchronized (mCache) {
157 if (mCache.hasKeyValue(key, userId)) {
158 return mCache.peekKeyValue(key, defaultValue, userId);
159 }
160 version = mCache.getVersion();
161 }
162
Adrian Roos261d5ab2014-10-29 14:42:38 +0100163 Cursor cursor;
Adrian Roos3dcae682014-10-29 14:43:56 +0100164 Object result = DEFAULT;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100165 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
166 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
167 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
168 new String[] { Integer.toString(userId), key },
169 null, null, null)) != null) {
170 if (cursor.moveToFirst()) {
171 result = cursor.getString(0);
172 }
173 cursor.close();
174 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100175 mCache.putKeyValueIfUnchanged(key, result, userId, version);
176 return result == DEFAULT ? defaultValue : (String) result;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100177 }
178
Adrian Roos3dcae682014-10-29 14:43:56 +0100179 public void prefetchUser(int userId) {
180 int version;
181 synchronized (mCache) {
182 if (mCache.isFetched(userId)) {
183 return;
184 }
185 mCache.setFetched(userId);
186 version = mCache.getVersion();
187 }
188
189 Cursor cursor;
190 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
191 if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH,
192 COLUMN_USERID + "=?",
193 new String[] { Integer.toString(userId) },
194 null, null, null)) != null) {
195 while (cursor.moveToNext()) {
196 String key = cursor.getString(0);
197 String value = cursor.getString(1);
198 mCache.putKeyValueIfUnchanged(key, value, userId, version);
199 }
200 cursor.close();
201 }
202
203 // Populate cache by reading the password and pattern files.
Rubin Xu1de89b32016-11-30 20:03:13 +0000204 readCredentialHash(userId);
Adrian Roos3dcae682014-10-29 14:43:56 +0100205 }
206
Rubin Xu1de89b32016-11-30 20:03:13 +0000207 private CredentialHash readPasswordHashIfExists(int userId) {
Andres Morales8fa56652015-03-31 09:19:50 -0700208 byte[] stored = readFile(getLockPasswordFilename(userId));
Rubin Xu1de89b32016-11-30 20:03:13 +0000209 if (!ArrayUtils.isEmpty(stored)) {
210 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
211 CredentialHash.VERSION_GATEKEEPER);
Andres Morales8fa56652015-03-31 09:19:50 -0700212 }
213
214 stored = readFile(getLegacyLockPasswordFilename(userId));
Rubin Xu1de89b32016-11-30 20:03:13 +0000215 if (!ArrayUtils.isEmpty(stored)) {
216 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
217 CredentialHash.VERSION_LEGACY);
Andres Morales8fa56652015-03-31 09:19:50 -0700218 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100219 return null;
220 }
221
Rubin Xu1de89b32016-11-30 20:03:13 +0000222 private CredentialHash readPatternHashIfExists(int userId) {
Andres Morales8fa56652015-03-31 09:19:50 -0700223 byte[] stored = readFile(getLockPatternFilename(userId));
Rubin Xu1de89b32016-11-30 20:03:13 +0000224 if (!ArrayUtils.isEmpty(stored)) {
225 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
226 CredentialHash.VERSION_GATEKEEPER);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100227 }
Andres Morales8fa56652015-03-31 09:19:50 -0700228
Andres Moralese40bad82015-05-28 14:21:36 -0700229 stored = readFile(getBaseZeroLockPatternFilename(userId));
Rubin Xu1de89b32016-11-30 20:03:13 +0000230 if (!ArrayUtils.isEmpty(stored)) {
Andres Moralese40bad82015-05-28 14:21:36 -0700231 return new CredentialHash(stored, true);
232 }
233
Andres Morales8fa56652015-03-31 09:19:50 -0700234 stored = readFile(getLegacyLockPatternFilename(userId));
Rubin Xu1de89b32016-11-30 20:03:13 +0000235 if (!ArrayUtils.isEmpty(stored)) {
236 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
237 CredentialHash.VERSION_LEGACY);
Andres Morales8fa56652015-03-31 09:19:50 -0700238 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100239 return null;
240 }
241
Rubin Xu1de89b32016-11-30 20:03:13 +0000242 public CredentialHash readCredentialHash(int userId) {
243 CredentialHash passwordHash = readPasswordHashIfExists(userId);
244 CredentialHash patternHash = readPatternHashIfExists(userId);
245 if (passwordHash != null && patternHash != null) {
246 if (passwordHash.version == CredentialHash.VERSION_GATEKEEPER) {
247 return passwordHash;
248 } else {
249 return patternHash;
250 }
251 } else if (passwordHash != null) {
252 return passwordHash;
253 } else if (patternHash != null) {
254 return patternHash;
255 } else {
256 return CredentialHash.createEmptyHash();
257 }
258 }
259
Ricky Waidc283a82016-03-24 19:55:08 +0000260 public void removeChildProfileLock(int userId) {
261 if (DEBUG)
262 Slog.e(TAG, "Remove child profile lock for user: " + userId);
263 try {
264 deleteFile(getChildProfileLockFile(userId));
265 } catch (Exception e) {
266 e.printStackTrace();
267 }
268 }
269
270 public void writeChildProfileLock(int userId, byte[] lock) {
271 writeFile(getChildProfileLockFile(userId), lock);
272 }
273
274 public byte[] readChildProfileLock(int userId) {
275 return readFile(getChildProfileLockFile(userId));
276 }
277
278 public boolean hasChildProfileLock(int userId) {
279 return hasFile(getChildProfileLockFile(userId));
280 }
Andres Moralese40bad82015-05-28 14:21:36 -0700281
Adrian Roos3dcae682014-10-29 14:43:56 +0100282 public boolean hasPassword(int userId) {
Andres Morales8fa56652015-03-31 09:19:50 -0700283 return hasFile(getLockPasswordFilename(userId)) ||
284 hasFile(getLegacyLockPasswordFilename(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100285 }
286
Adrian Roos3dcae682014-10-29 14:43:56 +0100287 public boolean hasPattern(int userId) {
Andres Morales8fa56652015-03-31 09:19:50 -0700288 return hasFile(getLockPatternFilename(userId)) ||
Andres Moralese40bad82015-05-28 14:21:36 -0700289 hasFile(getBaseZeroLockPatternFilename(userId)) ||
Andres Morales8fa56652015-03-31 09:19:50 -0700290 hasFile(getLegacyLockPatternFilename(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100291 }
292
Rubin Xua55b1682017-01-31 10:06:56 +0000293 public boolean hasCredential(int userId) {
294 return hasPassword(userId) || hasPattern(userId);
295 }
296
Adrian Roos3dcae682014-10-29 14:43:56 +0100297 private boolean hasFile(String name) {
298 byte[] contents = readFile(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100299 return contents != null && contents.length > 0;
300 }
301
Adrian Roos3dcae682014-10-29 14:43:56 +0100302 private byte[] readFile(String name) {
303 int version;
304 synchronized (mCache) {
305 if (mCache.hasFile(name)) {
306 return mCache.peekFile(name);
307 }
308 version = mCache.getVersion();
309 }
310
Adrian Roos261d5ab2014-10-29 14:42:38 +0100311 RandomAccessFile raf = null;
312 byte[] stored = null;
313 try {
314 raf = new RandomAccessFile(name, "r");
315 stored = new byte[(int) raf.length()];
316 raf.readFully(stored, 0, stored.length);
317 raf.close();
318 } catch (IOException e) {
319 Slog.e(TAG, "Cannot read file " + e);
320 } finally {
321 if (raf != null) {
322 try {
323 raf.close();
324 } catch (IOException e) {
325 Slog.e(TAG, "Error closing file " + e);
326 }
327 }
328 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100329 mCache.putFileIfUnchanged(name, stored, version);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100330 return stored;
331 }
332
Adrian Roos3dcae682014-10-29 14:43:56 +0100333 private void writeFile(String name, byte[] hash) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100334 synchronized (mFileWriteLock) {
335 RandomAccessFile raf = null;
336 try {
337 // Write the hash to file
338 raf = new RandomAccessFile(name, "rw");
339 // Truncate the file if pattern is null, to clear the lock
340 if (hash == null || hash.length == 0) {
341 raf.setLength(0);
342 } else {
343 raf.write(hash, 0, hash.length);
344 }
345 raf.close();
346 } catch (IOException e) {
347 Slog.e(TAG, "Error writing to file " + e);
348 } finally {
349 if (raf != null) {
350 try {
351 raf.close();
352 } catch (IOException e) {
353 Slog.e(TAG, "Error closing file " + e);
354 }
355 }
356 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100357 mCache.putFile(name, hash);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100358 }
359 }
360
Andres Moralese40bad82015-05-28 14:21:36 -0700361 private void deleteFile(String name) {
Ricky Waidc283a82016-03-24 19:55:08 +0000362 if (DEBUG) Slog.e(TAG, "Delete file " + name);
363 synchronized (mFileWriteLock) {
364 File file = new File(name);
365 if (file.exists()) {
366 file.delete();
367 mCache.putFile(name, null);
368 }
Andres Moralese40bad82015-05-28 14:21:36 -0700369 }
370 }
371
Rubin Xu1de89b32016-11-30 20:03:13 +0000372 public void writeCredentialHash(CredentialHash hash, int userId) {
373 byte[] patternHash = null;
374 byte[] passwordHash = null;
Robin Lee68e4ba42015-03-10 12:34:28 +0000375
Rubin Xu1de89b32016-11-30 20:03:13 +0000376 if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
377 passwordHash = hash.hash;
378 } else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
379 patternHash = hash.hash;
380 }
381 writeFile(getLockPasswordFilename(userId), passwordHash);
382 writeFile(getLockPatternFilename(userId), patternHash);
Robin Lee68e4ba42015-03-10 12:34:28 +0000383 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100384
Adrian Roose5424992014-11-07 21:47:17 +0100385 @VisibleForTesting
386 String getLockPatternFilename(int userId) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100387 return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100388 }
389
Adrian Roose5424992014-11-07 21:47:17 +0100390 @VisibleForTesting
391 String getLockPasswordFilename(int userId) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100392 return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
393 }
394
Andres Morales8fa56652015-03-31 09:19:50 -0700395 @VisibleForTesting
396 String getLegacyLockPatternFilename(int userId) {
397 return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE);
398 }
399
400 @VisibleForTesting
401 String getLegacyLockPasswordFilename(int userId) {
402 return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE);
403 }
404
Andres Moralese40bad82015-05-28 14:21:36 -0700405 private String getBaseZeroLockPatternFilename(int userId) {
406 return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE);
407 }
408
Ricky Waia46b40f2016-03-31 16:48:29 +0100409 @VisibleForTesting
410 String getChildProfileLockFile(int userId) {
Ricky Waidc283a82016-03-24 19:55:08 +0000411 return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
412 }
413
Adrian Roos3dcae682014-10-29 14:43:56 +0100414 private String getLockCredentialFilePathForUser(int userId, String basename) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100415 String dataSystemDirectory =
416 android.os.Environment.getDataDirectory().getAbsolutePath() +
417 SYSTEM_DIRECTORY;
418 if (userId == 0) {
419 // Leave it in the same place for user 0
Adrian Roos3dcae682014-10-29 14:43:56 +0100420 return dataSystemDirectory + basename;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100421 } else {
Adrian Roos3dcae682014-10-29 14:43:56 +0100422 return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
Adrian Roos261d5ab2014-10-29 14:42:38 +0100423 }
424 }
425
Adrian Roos261d5ab2014-10-29 14:42:38 +0100426 public void removeUser(int userId) {
427 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
428
429 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
430 final UserInfo parentInfo = um.getProfileParent(userId);
431
Robin Lee68e4ba42015-03-10 12:34:28 +0000432 if (parentInfo == null) {
433 // This user owns its lock settings files - safe to delete them
434 synchronized (mFileWriteLock) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100435 String name = getLockPasswordFilename(userId);
436 File 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 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100441 name = getLockPatternFilename(userId);
442 file = new File(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100443 if (file.exists()) {
444 file.delete();
Adrian Roos3dcae682014-10-29 14:43:56 +0100445 mCache.putFile(name, null);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100446 }
447 }
Ricky Waidc283a82016-03-24 19:55:08 +0000448 } else {
449 // Manged profile
450 removeChildProfileLock(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100451 }
452
453 try {
454 db.beginTransaction();
455 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
456 db.setTransactionSuccessful();
Adrian Roos3dcae682014-10-29 14:43:56 +0100457 mCache.removeUser(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100458 } finally {
459 db.endTransaction();
460 }
461 }
462
Adrian Roose5424992014-11-07 21:47:17 +0100463 @VisibleForTesting
464 void closeDatabase() {
465 mOpenHelper.close();
466 }
467
468 @VisibleForTesting
469 void clearCache() {
470 mCache.clear();
471 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100472
Adrian Roos3dcae682014-10-29 14:43:56 +0100473 public interface Callback {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100474 void initialize(SQLiteDatabase db);
475 }
476
477 class DatabaseHelper extends SQLiteOpenHelper {
478 private static final String TAG = "LockSettingsDB";
479 private static final String DATABASE_NAME = "locksettings.db";
480
481 private static final int DATABASE_VERSION = 2;
482
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000483 private Callback mCallback;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100484
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000485 public DatabaseHelper(Context context) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100486 super(context, DATABASE_NAME, null, DATABASE_VERSION);
487 setWriteAheadLoggingEnabled(true);
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000488 }
489
490 public void setCallback(Callback callback) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100491 mCallback = callback;
492 }
493
494 private void createTable(SQLiteDatabase db) {
495 db.execSQL("CREATE TABLE " + TABLE + " (" +
496 "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
497 COLUMN_KEY + " TEXT," +
498 COLUMN_USERID + " INTEGER," +
499 COLUMN_VALUE + " TEXT" +
500 ");");
501 }
502
503 @Override
504 public void onCreate(SQLiteDatabase db) {
505 createTable(db);
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000506 if (mCallback != null) {
507 mCallback.initialize(db);
508 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100509 }
510
511 @Override
512 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
513 int upgradeVersion = oldVersion;
514 if (upgradeVersion == 1) {
515 // Previously migrated lock screen widget settings. Now defunct.
516 upgradeVersion = 2;
517 }
518
519 if (upgradeVersion != DATABASE_VERSION) {
520 Log.w(TAG, "Failed to upgrade database!");
521 }
522 }
523 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100524
525 /**
526 * Cache consistency model:
527 * - Writes to storage write directly to the cache, but this MUST happen within the atomic
528 * section either provided by the database transaction or mWriteLock, such that writes to the
529 * cache and writes to the backing storage are guaranteed to occur in the same order
530 *
531 * - Reads can populate the cache, but because they are no strong ordering guarantees with
532 * respect to writes this precaution is taken:
533 * - The cache is assigned a version number that increases every time the cache is modified.
534 * Reads from backing storage can only populate the cache if the backing storage
535 * has not changed since the load operation has begun.
536 * This guarantees that no read operation can shadow a write to the cache that happens
537 * after it had begun.
538 */
539 private static class Cache {
540 private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
541 private final CacheKey mCacheKey = new CacheKey();
542 private int mVersion = 0;
543
544 String peekKeyValue(String key, String defaultValue, int userId) {
545 Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
546 return cached == DEFAULT ? defaultValue : (String) cached;
547 }
548
549 boolean hasKeyValue(String key, int userId) {
550 return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
551 }
552
553 void putKeyValue(String key, String value, int userId) {
554 put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
555 }
556
557 void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
558 putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
559 }
560
561 byte[] peekFile(String fileName) {
562 return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
563 }
564
565 boolean hasFile(String fileName) {
566 return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
567 }
568
569 void putFile(String key, byte[] value) {
570 put(CacheKey.TYPE_FILE, key, value, -1 /* userId */);
571 }
572
573 void putFileIfUnchanged(String key, byte[] value, int version) {
574 putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version);
575 }
576
577 void setFetched(int userId) {
578 put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
579 }
580
581 boolean isFetched(int userId) {
582 return contains(CacheKey.TYPE_FETCHED, "", userId);
583 }
584
585
586 private synchronized void put(int type, String key, Object value, int userId) {
587 // Create a new CachKey here because it may be saved in the map if the key is absent.
588 mCache.put(new CacheKey().set(type, key, userId), value);
589 mVersion++;
590 }
591
592 private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
593 int version) {
594 if (!contains(type, key, userId) && mVersion == version) {
595 put(type, key, value, userId);
596 }
597 }
598
599 private synchronized boolean contains(int type, String key, int userId) {
600 return mCache.containsKey(mCacheKey.set(type, key, userId));
601 }
602
603 private synchronized Object peek(int type, String key, int userId) {
604 return mCache.get(mCacheKey.set(type, key, userId));
605 }
606
607 private synchronized int getVersion() {
608 return mVersion;
609 }
610
611 synchronized void removeUser(int userId) {
612 for (int i = mCache.size() - 1; i >= 0; i--) {
613 if (mCache.keyAt(i).userId == userId) {
614 mCache.removeAt(i);
615 }
616 }
617
618 // Make sure in-flight loads can't write to cache.
619 mVersion++;
620 }
621
Adrian Roose5424992014-11-07 21:47:17 +0100622 synchronized void clear() {
623 mCache.clear();
624 mVersion++;
625 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100626
627 private static final class CacheKey {
628 static final int TYPE_KEY_VALUE = 0;
629 static final int TYPE_FILE = 1;
630 static final int TYPE_FETCHED = 2;
631
632 String key;
633 int userId;
634 int type;
635
636 public CacheKey set(int type, String key, int userId) {
637 this.type = type;
638 this.key = key;
639 this.userId = userId;
640 return this;
641 }
642
643 @Override
644 public boolean equals(Object obj) {
645 if (!(obj instanceof CacheKey))
646 return false;
647 CacheKey o = (CacheKey) obj;
648 return userId == o.userId && type == o.type && key.equals(o.key);
649 }
650
651 @Override
652 public int hashCode() {
653 return key.hashCode() ^ userId ^ type;
654 }
655 }
656 }
Rubin Xua55b1682017-01-31 10:06:56 +0000657
Adrian Roos261d5ab2014-10-29 14:42:38 +0100658}