blob: f5bae7c14d97521526c3ef94ae8b305f6d589c8f [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
Rubin Xu3bf722a2016-12-15 16:07:38 +000069 private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
70
Adrian Roos3dcae682014-10-29 14:43:56 +010071 private static final Object DEFAULT = new Object();
72
Adrian Roos261d5ab2014-10-29 14:42:38 +010073 private final DatabaseHelper mOpenHelper;
74 private final Context mContext;
Adrian Roos3dcae682014-10-29 14:43:56 +010075 private final Cache mCache = new Cache();
Adrian Roos261d5ab2014-10-29 14:42:38 +010076 private final Object mFileWriteLock = new Object();
77
Rubin Xu1de89b32016-11-30 20:03:13 +000078 @VisibleForTesting
79 public static class CredentialHash {
Andres Morales8fa56652015-03-31 09:19:50 -070080 static final int VERSION_LEGACY = 0;
81 static final int VERSION_GATEKEEPER = 1;
82
Rubin Xu1de89b32016-11-30 20:03:13 +000083 private CredentialHash(byte[] hash, int type, int version) {
84 if (type != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
85 if (hash == null) {
86 throw new RuntimeException("Empty hash for CredentialHash");
87 }
88 } else /* type == LockPatternUtils.CREDENTIAL_TYPE_NONE */ {
89 if (hash != null) {
90 throw new RuntimeException("None type CredentialHash should not have hash");
91 }
92 }
Andres Morales8fa56652015-03-31 09:19:50 -070093 this.hash = hash;
Rubin Xu1de89b32016-11-30 20:03:13 +000094 this.type = type;
Andres Morales8fa56652015-03-31 09:19:50 -070095 this.version = version;
Andres Moralese40bad82015-05-28 14:21:36 -070096 this.isBaseZeroPattern = false;
97 }
98
Rubin Xu1de89b32016-11-30 20:03:13 +000099 private CredentialHash(byte[] hash, boolean isBaseZeroPattern) {
Andres Moralese40bad82015-05-28 14:21:36 -0700100 this.hash = hash;
Rubin Xu1de89b32016-11-30 20:03:13 +0000101 this.type = LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
Andres Moralese40bad82015-05-28 14:21:36 -0700102 this.version = VERSION_GATEKEEPER;
103 this.isBaseZeroPattern = isBaseZeroPattern;
Andres Morales8fa56652015-03-31 09:19:50 -0700104 }
105
Rubin Xu1de89b32016-11-30 20:03:13 +0000106 static CredentialHash create(byte[] hash, int type) {
107 if (type == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
108 throw new RuntimeException("Bad type for CredentialHash");
109 }
110 return new CredentialHash(hash, type, VERSION_GATEKEEPER);
111 }
112
113 static CredentialHash createEmptyHash() {
114 return new CredentialHash(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
115 VERSION_GATEKEEPER);
116 }
117
Andres Morales8fa56652015-03-31 09:19:50 -0700118 byte[] hash;
Rubin Xu1de89b32016-11-30 20:03:13 +0000119 int type;
Andres Morales8fa56652015-03-31 09:19:50 -0700120 int version;
Andres Moralese40bad82015-05-28 14:21:36 -0700121 boolean isBaseZeroPattern;
Andres Morales8fa56652015-03-31 09:19:50 -0700122 }
123
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000124 public LockSettingsStorage(Context context) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100125 mContext = context;
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000126 mOpenHelper = new DatabaseHelper(context);
127 }
128
129 public void setDatabaseOnCreateCallback(Callback callback) {
130 mOpenHelper.setCallback(callback);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100131 }
132
Adrian Roos3dcae682014-10-29 14:43:56 +0100133 public void writeKeyValue(String key, String value, int userId) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100134 writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
135 }
136
Adrian Roos3dcae682014-10-29 14:43:56 +0100137 public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100138 ContentValues cv = new ContentValues();
139 cv.put(COLUMN_KEY, key);
140 cv.put(COLUMN_USERID, userId);
141 cv.put(COLUMN_VALUE, value);
142
143 db.beginTransaction();
144 try {
145 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
146 new String[] {key, Integer.toString(userId)});
147 db.insert(TABLE, null, cv);
148 db.setTransactionSuccessful();
Adrian Roos3dcae682014-10-29 14:43:56 +0100149 mCache.putKeyValue(key, value, userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100150 } finally {
151 db.endTransaction();
152 }
153
154 }
155
Adrian Roos3dcae682014-10-29 14:43:56 +0100156 public String readKeyValue(String key, String defaultValue, int userId) {
157 int version;
158 synchronized (mCache) {
159 if (mCache.hasKeyValue(key, userId)) {
160 return mCache.peekKeyValue(key, defaultValue, userId);
161 }
162 version = mCache.getVersion();
163 }
164
Adrian Roos261d5ab2014-10-29 14:42:38 +0100165 Cursor cursor;
Adrian Roos3dcae682014-10-29 14:43:56 +0100166 Object result = DEFAULT;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100167 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
168 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
169 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
170 new String[] { Integer.toString(userId), key },
171 null, null, null)) != null) {
172 if (cursor.moveToFirst()) {
173 result = cursor.getString(0);
174 }
175 cursor.close();
176 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100177 mCache.putKeyValueIfUnchanged(key, result, userId, version);
178 return result == DEFAULT ? defaultValue : (String) result;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100179 }
180
Adrian Roos3dcae682014-10-29 14:43:56 +0100181 public void prefetchUser(int userId) {
182 int version;
183 synchronized (mCache) {
184 if (mCache.isFetched(userId)) {
185 return;
186 }
187 mCache.setFetched(userId);
188 version = mCache.getVersion();
189 }
190
191 Cursor cursor;
192 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
193 if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH,
194 COLUMN_USERID + "=?",
195 new String[] { Integer.toString(userId) },
196 null, null, null)) != null) {
197 while (cursor.moveToNext()) {
198 String key = cursor.getString(0);
199 String value = cursor.getString(1);
200 mCache.putKeyValueIfUnchanged(key, value, userId, version);
201 }
202 cursor.close();
203 }
204
205 // Populate cache by reading the password and pattern files.
Rubin Xu1de89b32016-11-30 20:03:13 +0000206 readCredentialHash(userId);
Adrian Roos3dcae682014-10-29 14:43:56 +0100207 }
208
Rubin Xu1de89b32016-11-30 20:03:13 +0000209 private CredentialHash readPasswordHashIfExists(int userId) {
Andres Morales8fa56652015-03-31 09:19:50 -0700210 byte[] stored = readFile(getLockPasswordFilename(userId));
Rubin Xu1de89b32016-11-30 20:03:13 +0000211 if (!ArrayUtils.isEmpty(stored)) {
212 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
213 CredentialHash.VERSION_GATEKEEPER);
Andres Morales8fa56652015-03-31 09:19:50 -0700214 }
215
216 stored = readFile(getLegacyLockPasswordFilename(userId));
Rubin Xu1de89b32016-11-30 20:03:13 +0000217 if (!ArrayUtils.isEmpty(stored)) {
218 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
219 CredentialHash.VERSION_LEGACY);
Andres Morales8fa56652015-03-31 09:19:50 -0700220 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100221 return null;
222 }
223
Rubin Xu1de89b32016-11-30 20:03:13 +0000224 private CredentialHash readPatternHashIfExists(int userId) {
Andres Morales8fa56652015-03-31 09:19:50 -0700225 byte[] stored = readFile(getLockPatternFilename(userId));
Rubin Xu1de89b32016-11-30 20:03:13 +0000226 if (!ArrayUtils.isEmpty(stored)) {
227 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
228 CredentialHash.VERSION_GATEKEEPER);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100229 }
Andres Morales8fa56652015-03-31 09:19:50 -0700230
Andres Moralese40bad82015-05-28 14:21:36 -0700231 stored = readFile(getBaseZeroLockPatternFilename(userId));
Rubin Xu1de89b32016-11-30 20:03:13 +0000232 if (!ArrayUtils.isEmpty(stored)) {
Andres Moralese40bad82015-05-28 14:21:36 -0700233 return new CredentialHash(stored, true);
234 }
235
Andres Morales8fa56652015-03-31 09:19:50 -0700236 stored = readFile(getLegacyLockPatternFilename(userId));
Rubin Xu1de89b32016-11-30 20:03:13 +0000237 if (!ArrayUtils.isEmpty(stored)) {
238 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
239 CredentialHash.VERSION_LEGACY);
Andres Morales8fa56652015-03-31 09:19:50 -0700240 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100241 return null;
242 }
243
Rubin Xu1de89b32016-11-30 20:03:13 +0000244 public CredentialHash readCredentialHash(int userId) {
245 CredentialHash passwordHash = readPasswordHashIfExists(userId);
246 CredentialHash patternHash = readPatternHashIfExists(userId);
247 if (passwordHash != null && patternHash != null) {
248 if (passwordHash.version == CredentialHash.VERSION_GATEKEEPER) {
249 return passwordHash;
250 } else {
251 return patternHash;
252 }
253 } else if (passwordHash != null) {
254 return passwordHash;
255 } else if (patternHash != null) {
256 return patternHash;
257 } else {
258 return CredentialHash.createEmptyHash();
259 }
260 }
261
Ricky Waidc283a82016-03-24 19:55:08 +0000262 public void removeChildProfileLock(int userId) {
263 if (DEBUG)
264 Slog.e(TAG, "Remove child profile lock for user: " + userId);
265 try {
266 deleteFile(getChildProfileLockFile(userId));
267 } catch (Exception e) {
268 e.printStackTrace();
269 }
270 }
271
272 public void writeChildProfileLock(int userId, byte[] lock) {
273 writeFile(getChildProfileLockFile(userId), lock);
274 }
275
276 public byte[] readChildProfileLock(int userId) {
277 return readFile(getChildProfileLockFile(userId));
278 }
279
280 public boolean hasChildProfileLock(int userId) {
281 return hasFile(getChildProfileLockFile(userId));
282 }
Andres Moralese40bad82015-05-28 14:21:36 -0700283
Adrian Roos3dcae682014-10-29 14:43:56 +0100284 public boolean hasPassword(int userId) {
Andres Morales8fa56652015-03-31 09:19:50 -0700285 return hasFile(getLockPasswordFilename(userId)) ||
286 hasFile(getLegacyLockPasswordFilename(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100287 }
288
Adrian Roos3dcae682014-10-29 14:43:56 +0100289 public boolean hasPattern(int userId) {
Andres Morales8fa56652015-03-31 09:19:50 -0700290 return hasFile(getLockPatternFilename(userId)) ||
Andres Moralese40bad82015-05-28 14:21:36 -0700291 hasFile(getBaseZeroLockPatternFilename(userId)) ||
Andres Morales8fa56652015-03-31 09:19:50 -0700292 hasFile(getLegacyLockPatternFilename(userId));
Adrian Roos261d5ab2014-10-29 14:42:38 +0100293 }
294
Rubin Xua55b1682017-01-31 10:06:56 +0000295 public boolean hasCredential(int userId) {
296 return hasPassword(userId) || hasPattern(userId);
297 }
298
Adrian Roos3dcae682014-10-29 14:43:56 +0100299 private boolean hasFile(String name) {
300 byte[] contents = readFile(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100301 return contents != null && contents.length > 0;
302 }
303
Adrian Roos3dcae682014-10-29 14:43:56 +0100304 private byte[] readFile(String name) {
305 int version;
306 synchronized (mCache) {
307 if (mCache.hasFile(name)) {
308 return mCache.peekFile(name);
309 }
310 version = mCache.getVersion();
311 }
312
Adrian Roos261d5ab2014-10-29 14:42:38 +0100313 RandomAccessFile raf = null;
314 byte[] stored = null;
315 try {
316 raf = new RandomAccessFile(name, "r");
317 stored = new byte[(int) raf.length()];
318 raf.readFully(stored, 0, stored.length);
319 raf.close();
320 } catch (IOException e) {
321 Slog.e(TAG, "Cannot read file " + e);
322 } finally {
323 if (raf != null) {
324 try {
325 raf.close();
326 } catch (IOException e) {
327 Slog.e(TAG, "Error closing file " + e);
328 }
329 }
330 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100331 mCache.putFileIfUnchanged(name, stored, version);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100332 return stored;
333 }
334
Adrian Roos3dcae682014-10-29 14:43:56 +0100335 private void writeFile(String name, byte[] hash) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100336 synchronized (mFileWriteLock) {
337 RandomAccessFile raf = null;
338 try {
339 // Write the hash to file
340 raf = new RandomAccessFile(name, "rw");
341 // Truncate the file if pattern is null, to clear the lock
342 if (hash == null || hash.length == 0) {
343 raf.setLength(0);
344 } else {
345 raf.write(hash, 0, hash.length);
346 }
347 raf.close();
348 } catch (IOException e) {
349 Slog.e(TAG, "Error writing to file " + e);
350 } finally {
351 if (raf != null) {
352 try {
353 raf.close();
354 } catch (IOException e) {
355 Slog.e(TAG, "Error closing file " + e);
356 }
357 }
358 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100359 mCache.putFile(name, hash);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100360 }
361 }
362
Andres Moralese40bad82015-05-28 14:21:36 -0700363 private void deleteFile(String name) {
Ricky Waidc283a82016-03-24 19:55:08 +0000364 if (DEBUG) Slog.e(TAG, "Delete file " + name);
365 synchronized (mFileWriteLock) {
366 File file = new File(name);
367 if (file.exists()) {
368 file.delete();
369 mCache.putFile(name, null);
370 }
Andres Moralese40bad82015-05-28 14:21:36 -0700371 }
372 }
373
Rubin Xu1de89b32016-11-30 20:03:13 +0000374 public void writeCredentialHash(CredentialHash hash, int userId) {
375 byte[] patternHash = null;
376 byte[] passwordHash = null;
Robin Lee68e4ba42015-03-10 12:34:28 +0000377
Rubin Xu1de89b32016-11-30 20:03:13 +0000378 if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
379 passwordHash = hash.hash;
380 } else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
381 patternHash = hash.hash;
382 }
383 writeFile(getLockPasswordFilename(userId), passwordHash);
384 writeFile(getLockPatternFilename(userId), patternHash);
Robin Lee68e4ba42015-03-10 12:34:28 +0000385 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100386
Adrian Roose5424992014-11-07 21:47:17 +0100387 @VisibleForTesting
388 String getLockPatternFilename(int userId) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100389 return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100390 }
391
Adrian Roose5424992014-11-07 21:47:17 +0100392 @VisibleForTesting
393 String getLockPasswordFilename(int userId) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100394 return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
395 }
396
Andres Morales8fa56652015-03-31 09:19:50 -0700397 @VisibleForTesting
398 String getLegacyLockPatternFilename(int userId) {
399 return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE);
400 }
401
402 @VisibleForTesting
403 String getLegacyLockPasswordFilename(int userId) {
404 return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE);
405 }
406
Andres Moralese40bad82015-05-28 14:21:36 -0700407 private String getBaseZeroLockPatternFilename(int userId) {
408 return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE);
409 }
410
Ricky Waia46b40f2016-03-31 16:48:29 +0100411 @VisibleForTesting
412 String getChildProfileLockFile(int userId) {
Ricky Waidc283a82016-03-24 19:55:08 +0000413 return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
414 }
415
Adrian Roos3dcae682014-10-29 14:43:56 +0100416 private String getLockCredentialFilePathForUser(int userId, String basename) {
Rubin Xu3bf722a2016-12-15 16:07:38 +0000417 String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() +
Adrian Roos261d5ab2014-10-29 14:42:38 +0100418 SYSTEM_DIRECTORY;
419 if (userId == 0) {
420 // Leave it in the same place for user 0
Adrian Roos3dcae682014-10-29 14:43:56 +0100421 return dataSystemDirectory + basename;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100422 } else {
Adrian Roos3dcae682014-10-29 14:43:56 +0100423 return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
Adrian Roos261d5ab2014-10-29 14:42:38 +0100424 }
425 }
426
Rubin Xu3bf722a2016-12-15 16:07:38 +0000427 public void writeSyntheticPasswordState(int userId, long handle, String name, byte[] data) {
428 writeFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name), data);
429 }
430
431 public byte[] readSyntheticPasswordState(int userId, long handle, String name) {
432 return readFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name));
433 }
434
435 public void deleteSyntheticPasswordState(int userId, long handle, String name, boolean secure) {
436 String path = getSynthenticPasswordStateFilePathForUser(userId, handle, name);
437 File file = new File(path);
438 if (file.exists()) {
439 //TODO: (b/34600579) invoke secdiscardable
440 file.delete();
441 mCache.putFile(path, null);
442 }
443 }
444
445 @VisibleForTesting
446 protected File getSyntheticPasswordDirectoryForUser(int userId) {
447 return new File(Environment.getDataSystemDeDirectory(userId) ,SYNTHETIC_PASSWORD_DIRECTORY);
448 }
449
450 @VisibleForTesting
451 protected String getSynthenticPasswordStateFilePathForUser(int userId, long handle,
452 String name) {
453 File baseDir = getSyntheticPasswordDirectoryForUser(userId);
454 String baseName = String.format("%016x.%s", handle, name);
455 if (!baseDir.exists()) {
456 baseDir.mkdir();
457 }
458 return new File(baseDir, baseName).getAbsolutePath();
459 }
460
Adrian Roos261d5ab2014-10-29 14:42:38 +0100461 public void removeUser(int userId) {
462 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
463
464 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
465 final UserInfo parentInfo = um.getProfileParent(userId);
466
Robin Lee68e4ba42015-03-10 12:34:28 +0000467 if (parentInfo == null) {
468 // This user owns its lock settings files - safe to delete them
469 synchronized (mFileWriteLock) {
Adrian Roos3dcae682014-10-29 14:43:56 +0100470 String name = getLockPasswordFilename(userId);
471 File file = new File(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100472 if (file.exists()) {
473 file.delete();
Adrian Roos3dcae682014-10-29 14:43:56 +0100474 mCache.putFile(name, null);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100475 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100476 name = getLockPatternFilename(userId);
477 file = new File(name);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100478 if (file.exists()) {
479 file.delete();
Adrian Roos3dcae682014-10-29 14:43:56 +0100480 mCache.putFile(name, null);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100481 }
482 }
Ricky Waidc283a82016-03-24 19:55:08 +0000483 } else {
Rubin Xu3bf722a2016-12-15 16:07:38 +0000484 // Managed profile
Ricky Waidc283a82016-03-24 19:55:08 +0000485 removeChildProfileLock(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100486 }
487
Rubin Xu3bf722a2016-12-15 16:07:38 +0000488 File spStateDir = getSyntheticPasswordDirectoryForUser(userId);
Adrian Roos261d5ab2014-10-29 14:42:38 +0100489 try {
490 db.beginTransaction();
491 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
492 db.setTransactionSuccessful();
Adrian Roos3dcae682014-10-29 14:43:56 +0100493 mCache.removeUser(userId);
Rubin Xu3bf722a2016-12-15 16:07:38 +0000494 // The directory itself will be deleted as part of user deletion operation by the
495 // framework, so only need to purge cache here.
496 //TODO: (b/34600579) invoke secdiscardable
497 mCache.purgePath(spStateDir.getAbsolutePath());
Adrian Roos261d5ab2014-10-29 14:42:38 +0100498 } finally {
499 db.endTransaction();
500 }
501 }
502
Adrian Roose5424992014-11-07 21:47:17 +0100503 @VisibleForTesting
504 void closeDatabase() {
505 mOpenHelper.close();
506 }
507
508 @VisibleForTesting
509 void clearCache() {
510 mCache.clear();
511 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100512
Adrian Roos3dcae682014-10-29 14:43:56 +0100513 public interface Callback {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100514 void initialize(SQLiteDatabase db);
515 }
516
517 class DatabaseHelper extends SQLiteOpenHelper {
518 private static final String TAG = "LockSettingsDB";
519 private static final String DATABASE_NAME = "locksettings.db";
520
521 private static final int DATABASE_VERSION = 2;
522
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000523 private Callback mCallback;
Adrian Roos261d5ab2014-10-29 14:42:38 +0100524
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000525 public DatabaseHelper(Context context) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100526 super(context, DATABASE_NAME, null, DATABASE_VERSION);
527 setWriteAheadLoggingEnabled(true);
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000528 }
529
530 public void setCallback(Callback callback) {
Adrian Roos261d5ab2014-10-29 14:42:38 +0100531 mCallback = callback;
532 }
533
534 private void createTable(SQLiteDatabase db) {
535 db.execSQL("CREATE TABLE " + TABLE + " (" +
536 "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
537 COLUMN_KEY + " TEXT," +
538 COLUMN_USERID + " INTEGER," +
539 COLUMN_VALUE + " TEXT" +
540 ");");
541 }
542
543 @Override
544 public void onCreate(SQLiteDatabase db) {
545 createTable(db);
Rubin Xu0cbc19e2016-12-09 14:00:21 +0000546 if (mCallback != null) {
547 mCallback.initialize(db);
548 }
Adrian Roos261d5ab2014-10-29 14:42:38 +0100549 }
550
551 @Override
552 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
553 int upgradeVersion = oldVersion;
554 if (upgradeVersion == 1) {
555 // Previously migrated lock screen widget settings. Now defunct.
556 upgradeVersion = 2;
557 }
558
559 if (upgradeVersion != DATABASE_VERSION) {
560 Log.w(TAG, "Failed to upgrade database!");
561 }
562 }
563 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100564
565 /**
566 * Cache consistency model:
567 * - Writes to storage write directly to the cache, but this MUST happen within the atomic
568 * section either provided by the database transaction or mWriteLock, such that writes to the
569 * cache and writes to the backing storage are guaranteed to occur in the same order
570 *
571 * - Reads can populate the cache, but because they are no strong ordering guarantees with
572 * respect to writes this precaution is taken:
573 * - The cache is assigned a version number that increases every time the cache is modified.
574 * Reads from backing storage can only populate the cache if the backing storage
575 * has not changed since the load operation has begun.
576 * This guarantees that no read operation can shadow a write to the cache that happens
577 * after it had begun.
578 */
579 private static class Cache {
580 private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
581 private final CacheKey mCacheKey = new CacheKey();
582 private int mVersion = 0;
583
584 String peekKeyValue(String key, String defaultValue, int userId) {
585 Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
586 return cached == DEFAULT ? defaultValue : (String) cached;
587 }
588
589 boolean hasKeyValue(String key, int userId) {
590 return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
591 }
592
593 void putKeyValue(String key, String value, int userId) {
594 put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
595 }
596
597 void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
598 putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
599 }
600
601 byte[] peekFile(String fileName) {
602 return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
603 }
604
605 boolean hasFile(String fileName) {
606 return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
607 }
608
609 void putFile(String key, byte[] value) {
610 put(CacheKey.TYPE_FILE, key, value, -1 /* userId */);
611 }
612
613 void putFileIfUnchanged(String key, byte[] value, int version) {
614 putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version);
615 }
616
617 void setFetched(int userId) {
618 put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
619 }
620
621 boolean isFetched(int userId) {
622 return contains(CacheKey.TYPE_FETCHED, "", userId);
623 }
624
625
626 private synchronized void put(int type, String key, Object value, int userId) {
627 // Create a new CachKey here because it may be saved in the map if the key is absent.
628 mCache.put(new CacheKey().set(type, key, userId), value);
629 mVersion++;
630 }
631
632 private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
633 int version) {
634 if (!contains(type, key, userId) && mVersion == version) {
635 put(type, key, value, userId);
636 }
637 }
638
639 private synchronized boolean contains(int type, String key, int userId) {
640 return mCache.containsKey(mCacheKey.set(type, key, userId));
641 }
642
643 private synchronized Object peek(int type, String key, int userId) {
644 return mCache.get(mCacheKey.set(type, key, userId));
645 }
646
647 private synchronized int getVersion() {
648 return mVersion;
649 }
650
651 synchronized void removeUser(int userId) {
652 for (int i = mCache.size() - 1; i >= 0; i--) {
653 if (mCache.keyAt(i).userId == userId) {
654 mCache.removeAt(i);
655 }
656 }
657
658 // Make sure in-flight loads can't write to cache.
659 mVersion++;
660 }
661
Rubin Xu3bf722a2016-12-15 16:07:38 +0000662 synchronized void purgePath(String path) {
663 for (int i = mCache.size() - 1; i >= 0; i--) {
664 CacheKey entry = mCache.keyAt(i);
665 if (entry.type == CacheKey.TYPE_FILE && entry.key.startsWith(path)) {
666 mCache.removeAt(i);
667 }
668 }
669 mVersion++;
670 }
671
Adrian Roose5424992014-11-07 21:47:17 +0100672 synchronized void clear() {
673 mCache.clear();
674 mVersion++;
675 }
Adrian Roos3dcae682014-10-29 14:43:56 +0100676
677 private static final class CacheKey {
678 static final int TYPE_KEY_VALUE = 0;
679 static final int TYPE_FILE = 1;
680 static final int TYPE_FETCHED = 2;
681
682 String key;
683 int userId;
684 int type;
685
686 public CacheKey set(int type, String key, int userId) {
687 this.type = type;
688 this.key = key;
689 this.userId = userId;
690 return this;
691 }
692
693 @Override
694 public boolean equals(Object obj) {
695 if (!(obj instanceof CacheKey))
696 return false;
697 CacheKey o = (CacheKey) obj;
698 return userId == o.userId && type == o.type && key.equals(o.key);
699 }
700
701 @Override
702 public int hashCode() {
703 return key.hashCode() ^ userId ^ type;
704 }
705 }
706 }
Rubin Xua55b1682017-01-31 10:06:56 +0000707
Adrian Roos261d5ab2014-10-29 14:42:38 +0100708}