* commit '7dc3d7033b4b7a90514772dbee9702257f1fe367': Add caching to LockSettingsStorage
This commit is contained in:
@@ -88,6 +88,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
|||||||
|
|
||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter();
|
||||||
filter.addAction(Intent.ACTION_USER_ADDED);
|
filter.addAction(Intent.ACTION_USER_ADDED);
|
||||||
|
filter.addAction(Intent.ACTION_USER_STARTING);
|
||||||
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
|
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
|
||||||
|
|
||||||
mStorage = new LockSettingsStorage(context, new LockSettingsStorage.Callback() {
|
mStorage = new LockSettingsStorage(context, new LockSettingsStorage.Callback() {
|
||||||
@@ -121,12 +122,16 @@ public class LockSettingsService extends ILockSettings.Stub {
|
|||||||
final int parentSysUid = UserHandle.getUid(parentInfo.id, Process.SYSTEM_UID);
|
final int parentSysUid = UserHandle.getUid(parentInfo.id, Process.SYSTEM_UID);
|
||||||
ks.syncUid(parentSysUid, userSysUid);
|
ks.syncUid(parentSysUid, userSysUid);
|
||||||
}
|
}
|
||||||
|
} else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) {
|
||||||
|
final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
|
||||||
|
mStorage.prefetchUser(userHandle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public void systemReady() {
|
public void systemReady() {
|
||||||
migrateOldData();
|
migrateOldData();
|
||||||
|
mStorage.prefetchUser(UserHandle.USER_OWNER);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void migrateOldData() {
|
private void migrateOldData() {
|
||||||
|
|||||||
@@ -49,25 +49,31 @@ class LockSettingsStorage {
|
|||||||
private static final String[] COLUMNS_FOR_QUERY = {
|
private static final String[] COLUMNS_FOR_QUERY = {
|
||||||
COLUMN_VALUE
|
COLUMN_VALUE
|
||||||
};
|
};
|
||||||
|
private static final String[] COLUMNS_FOR_PREFETCH = {
|
||||||
|
COLUMN_KEY, COLUMN_VALUE
|
||||||
|
};
|
||||||
|
|
||||||
private static final String SYSTEM_DIRECTORY = "/system/";
|
private static final String SYSTEM_DIRECTORY = "/system/";
|
||||||
private static final String LOCK_PATTERN_FILE = "gesture.key";
|
private static final String LOCK_PATTERN_FILE = "gesture.key";
|
||||||
private static final String LOCK_PASSWORD_FILE = "password.key";
|
private static final String LOCK_PASSWORD_FILE = "password.key";
|
||||||
|
|
||||||
|
private static final Object DEFAULT = new Object();
|
||||||
|
|
||||||
private final DatabaseHelper mOpenHelper;
|
private final DatabaseHelper mOpenHelper;
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
private final Cache mCache = new Cache();
|
||||||
private final Object mFileWriteLock = new Object();
|
private final Object mFileWriteLock = new Object();
|
||||||
|
|
||||||
LockSettingsStorage(Context context, Callback callback) {
|
public LockSettingsStorage(Context context, Callback callback) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mOpenHelper = new DatabaseHelper(context, callback);
|
mOpenHelper = new DatabaseHelper(context, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeKeyValue(String key, String value, int userId) {
|
public void writeKeyValue(String key, String value, int userId) {
|
||||||
writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
|
writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
|
public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
|
||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
cv.put(COLUMN_KEY, key);
|
cv.put(COLUMN_KEY, key);
|
||||||
cv.put(COLUMN_USERID, userId);
|
cv.put(COLUMN_USERID, userId);
|
||||||
@@ -79,15 +85,24 @@ class LockSettingsStorage {
|
|||||||
new String[] {key, Integer.toString(userId)});
|
new String[] {key, Integer.toString(userId)});
|
||||||
db.insert(TABLE, null, cv);
|
db.insert(TABLE, null, cv);
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
|
mCache.putKeyValue(key, value, userId);
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String readKeyValue(String key, String defaultValue, int userId) {
|
public String readKeyValue(String key, String defaultValue, int userId) {
|
||||||
|
int version;
|
||||||
|
synchronized (mCache) {
|
||||||
|
if (mCache.hasKeyValue(key, userId)) {
|
||||||
|
return mCache.peekKeyValue(key, defaultValue, userId);
|
||||||
|
}
|
||||||
|
version = mCache.getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
Cursor cursor;
|
Cursor cursor;
|
||||||
String result = defaultValue;
|
Object result = DEFAULT;
|
||||||
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
|
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
|
||||||
if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
|
if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
|
||||||
COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
|
COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
|
||||||
@@ -98,39 +113,77 @@ class LockSettingsStorage {
|
|||||||
}
|
}
|
||||||
cursor.close();
|
cursor.close();
|
||||||
}
|
}
|
||||||
return result;
|
mCache.putKeyValueIfUnchanged(key, result, userId, version);
|
||||||
|
return result == DEFAULT ? defaultValue : (String) result;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] readPasswordHash(int userId) {
|
public void prefetchUser(int userId) {
|
||||||
final byte[] stored = readFile(getLockPasswordFilename(userId), userId);
|
int version;
|
||||||
|
synchronized (mCache) {
|
||||||
|
if (mCache.isFetched(userId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mCache.setFetched(userId);
|
||||||
|
version = mCache.getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor cursor;
|
||||||
|
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
|
||||||
|
if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH,
|
||||||
|
COLUMN_USERID + "=?",
|
||||||
|
new String[] { Integer.toString(userId) },
|
||||||
|
null, null, null)) != null) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
String key = cursor.getString(0);
|
||||||
|
String value = cursor.getString(1);
|
||||||
|
mCache.putKeyValueIfUnchanged(key, value, userId, version);
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate cache by reading the password and pattern files.
|
||||||
|
readPasswordHash(userId);
|
||||||
|
readPatternHash(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] readPasswordHash(int userId) {
|
||||||
|
final byte[] stored = readFile(getLockPasswordFilename(userId));
|
||||||
if (stored != null && stored.length > 0) {
|
if (stored != null && stored.length > 0) {
|
||||||
return stored;
|
return stored;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] readPatternHash(int userId) {
|
public byte[] readPatternHash(int userId) {
|
||||||
final byte[] stored = readFile(getLockPatternFilename(userId), userId);
|
final byte[] stored = readFile(getLockPatternFilename(userId));
|
||||||
if (stored != null && stored.length > 0) {
|
if (stored != null && stored.length > 0) {
|
||||||
return stored;
|
return stored;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasPassword(int userId) {
|
public boolean hasPassword(int userId) {
|
||||||
return hasFile(getLockPasswordFilename(userId), userId);
|
return hasFile(getLockPasswordFilename(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasPattern(int userId) {
|
public boolean hasPattern(int userId) {
|
||||||
return hasFile(getLockPatternFilename(userId), userId);
|
return hasFile(getLockPatternFilename(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasFile(String name, int userId) {
|
private boolean hasFile(String name) {
|
||||||
byte[] contents = readFile(name, userId);
|
byte[] contents = readFile(name);
|
||||||
return contents != null && contents.length > 0;
|
return contents != null && contents.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] readFile(String name, int userId) {
|
private byte[] readFile(String name) {
|
||||||
|
int version;
|
||||||
|
synchronized (mCache) {
|
||||||
|
if (mCache.hasFile(name)) {
|
||||||
|
return mCache.peekFile(name);
|
||||||
|
}
|
||||||
|
version = mCache.getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
RandomAccessFile raf = null;
|
RandomAccessFile raf = null;
|
||||||
byte[] stored = null;
|
byte[] stored = null;
|
||||||
try {
|
try {
|
||||||
@@ -149,10 +202,11 @@ class LockSettingsStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mCache.putFileIfUnchanged(name, stored, version);
|
||||||
return stored;
|
return stored;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeFile(String name, byte[] hash, int userId) {
|
private void writeFile(String name, byte[] hash) {
|
||||||
synchronized (mFileWriteLock) {
|
synchronized (mFileWriteLock) {
|
||||||
RandomAccessFile raf = null;
|
RandomAccessFile raf = null;
|
||||||
try {
|
try {
|
||||||
@@ -176,43 +230,37 @@ class LockSettingsStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mCache.putFile(name, hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writePatternHash(byte[] hash, int userId) {
|
public void writePatternHash(byte[] hash, int userId) {
|
||||||
writeFile(getLockPatternFilename(userId), hash, userId);
|
writeFile(getLockPatternFilename(userId), hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writePasswordHash(byte[] hash, int userId) {
|
public void writePasswordHash(byte[] hash, int userId) {
|
||||||
writeFile(getLockPasswordFilename(userId), hash, userId);
|
writeFile(getLockPasswordFilename(userId), hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private String getLockPatternFilename(int userId) {
|
private String getLockPatternFilename(int userId) {
|
||||||
String dataSystemDirectory =
|
return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
|
||||||
android.os.Environment.getDataDirectory().getAbsolutePath() +
|
|
||||||
SYSTEM_DIRECTORY;
|
|
||||||
userId = getUserParentOrSelfId(userId);
|
|
||||||
if (userId == 0) {
|
|
||||||
// Leave it in the same place for user 0
|
|
||||||
return dataSystemDirectory + LOCK_PATTERN_FILE;
|
|
||||||
} else {
|
|
||||||
return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
|
|
||||||
.getAbsolutePath();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getLockPasswordFilename(int userId) {
|
private String getLockPasswordFilename(int userId) {
|
||||||
|
return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLockCredentialFilePathForUser(int userId, String basename) {
|
||||||
userId = getUserParentOrSelfId(userId);
|
userId = getUserParentOrSelfId(userId);
|
||||||
String dataSystemDirectory =
|
String dataSystemDirectory =
|
||||||
android.os.Environment.getDataDirectory().getAbsolutePath() +
|
android.os.Environment.getDataDirectory().getAbsolutePath() +
|
||||||
SYSTEM_DIRECTORY;
|
SYSTEM_DIRECTORY;
|
||||||
if (userId == 0) {
|
if (userId == 0) {
|
||||||
// Leave it in the same place for user 0
|
// Leave it in the same place for user 0
|
||||||
return dataSystemDirectory + LOCK_PASSWORD_FILE;
|
return dataSystemDirectory + basename;
|
||||||
} else {
|
} else {
|
||||||
return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
|
return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
|
||||||
.getAbsolutePath();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,13 +285,17 @@ class LockSettingsStorage {
|
|||||||
synchronized (mFileWriteLock) {
|
synchronized (mFileWriteLock) {
|
||||||
if (parentInfo == null) {
|
if (parentInfo == null) {
|
||||||
// This user owns its lock settings files - safe to delete them
|
// This user owns its lock settings files - safe to delete them
|
||||||
File file = new File(getLockPasswordFilename(userId));
|
String name = getLockPasswordFilename(userId);
|
||||||
|
File file = new File(name);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
file.delete();
|
file.delete();
|
||||||
|
mCache.putFile(name, null);
|
||||||
}
|
}
|
||||||
file = new File(getLockPatternFilename(userId));
|
name = getLockPatternFilename(userId);
|
||||||
|
file = new File(name);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
file.delete();
|
file.delete();
|
||||||
|
mCache.putFile(name, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,13 +304,14 @@ class LockSettingsStorage {
|
|||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
|
db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
|
mCache.removeUser(userId);
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface Callback {
|
public interface Callback {
|
||||||
void initialize(SQLiteDatabase db);
|
void initialize(SQLiteDatabase db);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,4 +357,133 @@ class LockSettingsStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache consistency model:
|
||||||
|
* - Writes to storage write directly to the cache, but this MUST happen within the atomic
|
||||||
|
* section either provided by the database transaction or mWriteLock, such that writes to the
|
||||||
|
* cache and writes to the backing storage are guaranteed to occur in the same order
|
||||||
|
*
|
||||||
|
* - Reads can populate the cache, but because they are no strong ordering guarantees with
|
||||||
|
* respect to writes this precaution is taken:
|
||||||
|
* - The cache is assigned a version number that increases every time the cache is modified.
|
||||||
|
* Reads from backing storage can only populate the cache if the backing storage
|
||||||
|
* has not changed since the load operation has begun.
|
||||||
|
* This guarantees that no read operation can shadow a write to the cache that happens
|
||||||
|
* after it had begun.
|
||||||
|
*/
|
||||||
|
private static class Cache {
|
||||||
|
private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
|
||||||
|
private final CacheKey mCacheKey = new CacheKey();
|
||||||
|
private int mVersion = 0;
|
||||||
|
|
||||||
|
String peekKeyValue(String key, String defaultValue, int userId) {
|
||||||
|
Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
|
||||||
|
return cached == DEFAULT ? defaultValue : (String) cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasKeyValue(String key, int userId) {
|
||||||
|
return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void putKeyValue(String key, String value, int userId) {
|
||||||
|
put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
|
||||||
|
putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] peekFile(String fileName) {
|
||||||
|
return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasFile(String fileName) {
|
||||||
|
return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
|
||||||
|
}
|
||||||
|
|
||||||
|
void putFile(String key, byte[] value) {
|
||||||
|
put(CacheKey.TYPE_FILE, key, value, -1 /* userId */);
|
||||||
|
}
|
||||||
|
|
||||||
|
void putFileIfUnchanged(String key, byte[] value, int version) {
|
||||||
|
putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFetched(int userId) {
|
||||||
|
put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isFetched(int userId) {
|
||||||
|
return contains(CacheKey.TYPE_FETCHED, "", userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private synchronized void put(int type, String key, Object value, int userId) {
|
||||||
|
// Create a new CachKey here because it may be saved in the map if the key is absent.
|
||||||
|
mCache.put(new CacheKey().set(type, key, userId), value);
|
||||||
|
mVersion++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
|
||||||
|
int version) {
|
||||||
|
if (!contains(type, key, userId) && mVersion == version) {
|
||||||
|
put(type, key, value, userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized boolean contains(int type, String key, int userId) {
|
||||||
|
return mCache.containsKey(mCacheKey.set(type, key, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized Object peek(int type, String key, int userId) {
|
||||||
|
return mCache.get(mCacheKey.set(type, key, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized int getVersion() {
|
||||||
|
return mVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void removeUser(int userId) {
|
||||||
|
for (int i = mCache.size() - 1; i >= 0; i--) {
|
||||||
|
if (mCache.keyAt(i).userId == userId) {
|
||||||
|
mCache.removeAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure in-flight loads can't write to cache.
|
||||||
|
mVersion++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static final class CacheKey {
|
||||||
|
static final int TYPE_KEY_VALUE = 0;
|
||||||
|
static final int TYPE_FILE = 1;
|
||||||
|
static final int TYPE_FETCHED = 2;
|
||||||
|
|
||||||
|
String key;
|
||||||
|
int userId;
|
||||||
|
int type;
|
||||||
|
|
||||||
|
public CacheKey set(int type, String key, int userId) {
|
||||||
|
this.type = type;
|
||||||
|
this.key = key;
|
||||||
|
this.userId = userId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof CacheKey))
|
||||||
|
return false;
|
||||||
|
CacheKey o = (CacheKey) obj;
|
||||||
|
return userId == o.userId && type == o.type && key.equals(o.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return key.hashCode() ^ userId ^ type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user