LockSettingsStorage.java revision e542499a304f067372d85722e11a74b4e56b0bd7
1261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos/*
2261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos * Copyright (C) 2014 The Android Open Source Project
3261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos *
4261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos * Licensed under the Apache License, Version 2.0 (the "License");
5261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos * you may not use this file except in compliance with the License.
6261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos * You may obtain a copy of the License at
7261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos *
8261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos *      http://www.apache.org/licenses/LICENSE-2.0
9261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos *
10261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos * Unless required by applicable law or agreed to in writing, software
11261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos * distributed under the License is distributed on an "AS IS" BASIS,
12261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos * See the License for the specific language governing permissions and
14261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos * limitations under the License
15261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos */
16261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
17261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roospackage com.android.server;
18261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
19e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roosimport com.android.internal.annotations.VisibleForTesting;
20e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos
21261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport android.content.ContentValues;
22261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport android.content.Context;
23261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport android.content.pm.UserInfo;
24261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport android.database.Cursor;
25261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport android.database.sqlite.SQLiteDatabase;
26261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport android.database.sqlite.SQLiteOpenHelper;
27261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport android.os.Environment;
28261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport android.os.UserManager;
29261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport android.util.ArrayMap;
30261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport android.util.Log;
31261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport android.util.Slog;
32261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
33261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport java.io.File;
34261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport java.io.IOException;
35261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport java.io.RandomAccessFile;
36261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
37261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosimport static android.content.Context.USER_SERVICE;
38261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
39261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos/**
40261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos * Storage for the lock settings service.
41261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos */
42261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roosclass LockSettingsStorage {
43261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
44261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private static final String TAG = "LockSettingsStorage";
45261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private static final String TABLE = "locksettings";
46261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
47261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private static final String COLUMN_KEY = "name";
48261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private static final String COLUMN_USERID = "user";
49261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private static final String COLUMN_VALUE = "value";
50261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
51261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private static final String[] COLUMNS_FOR_QUERY = {
52261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            COLUMN_VALUE
53261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    };
543dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private static final String[] COLUMNS_FOR_PREFETCH = {
553dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            COLUMN_KEY, COLUMN_VALUE
563dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    };
57261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
58261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private static final String SYSTEM_DIRECTORY = "/system/";
59261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private static final String LOCK_PATTERN_FILE = "gesture.key";
60261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private static final String LOCK_PASSWORD_FILE = "password.key";
61261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
623dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private static final Object DEFAULT = new Object();
633dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
64261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private final DatabaseHelper mOpenHelper;
65261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private final Context mContext;
663dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private final Cache mCache = new Cache();
67261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private final Object mFileWriteLock = new Object();
68261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
693dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public LockSettingsStorage(Context context, Callback callback) {
70261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        mContext = context;
71261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        mOpenHelper = new DatabaseHelper(context, callback);
72261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
73261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
743dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public void writeKeyValue(String key, String value, int userId) {
75261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
76261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
77261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
783dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
79261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        ContentValues cv = new ContentValues();
80261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        cv.put(COLUMN_KEY, key);
81261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        cv.put(COLUMN_USERID, userId);
82261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        cv.put(COLUMN_VALUE, value);
83261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
84261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        db.beginTransaction();
85261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        try {
86261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
87261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    new String[] {key, Integer.toString(userId)});
88261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.insert(TABLE, null, cv);
89261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.setTransactionSuccessful();
903dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mCache.putKeyValue(key, value, userId);
91261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        } finally {
92261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.endTransaction();
93261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
94261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
95261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
96261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
973dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public String readKeyValue(String key, String defaultValue, int userId) {
983dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        int version;
993dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        synchronized (mCache) {
1003dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            if (mCache.hasKeyValue(key, userId)) {
1013dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                return mCache.peekKeyValue(key, defaultValue, userId);
1023dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
1033dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            version = mCache.getVersion();
1043dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
1053dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
106261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        Cursor cursor;
1073dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        Object result = DEFAULT;
108261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
109261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
110261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
111261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                new String[] { Integer.toString(userId), key },
112261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                null, null, null)) != null) {
113261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            if (cursor.moveToFirst()) {
114261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                result = cursor.getString(0);
115261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
116261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            cursor.close();
117261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
1183dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        mCache.putKeyValueIfUnchanged(key, result, userId, version);
1193dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        return result == DEFAULT ? defaultValue : (String) result;
1203dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    }
1213dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
1223dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public void prefetchUser(int userId) {
1233dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        int version;
1243dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        synchronized (mCache) {
1253dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            if (mCache.isFetched(userId)) {
1263dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                return;
1273dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
1283dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mCache.setFetched(userId);
1293dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            version = mCache.getVersion();
1303dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
1313dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
1323dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        Cursor cursor;
1333dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
1343dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH,
1353dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                COLUMN_USERID + "=?",
1363dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                new String[] { Integer.toString(userId) },
1373dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                null, null, null)) != null) {
1383dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            while (cursor.moveToNext()) {
1393dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                String key = cursor.getString(0);
1403dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                String value = cursor.getString(1);
1413dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                mCache.putKeyValueIfUnchanged(key, value, userId, version);
1423dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
1433dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            cursor.close();
1443dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
1453dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
1463dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        // Populate cache by reading the password and pattern files.
1473dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        readPasswordHash(userId);
1483dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        readPatternHash(userId);
149261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
150261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
1513dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public byte[] readPasswordHash(int userId) {
1523dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        final byte[] stored = readFile(getLockPasswordFilename(userId));
153261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        if (stored != null && stored.length > 0) {
154261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            return stored;
155261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
156261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        return null;
157261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
158261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
1593dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public byte[] readPatternHash(int userId) {
1603dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        final byte[] stored = readFile(getLockPatternFilename(userId));
161261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        if (stored != null && stored.length > 0) {
162261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            return stored;
163261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
164261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        return null;
165261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
166261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
1673dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public boolean hasPassword(int userId) {
1683dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        return hasFile(getLockPasswordFilename(userId));
169261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
170261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
1713dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public boolean hasPattern(int userId) {
1723dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        return hasFile(getLockPatternFilename(userId));
173261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
174261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
1753dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private boolean hasFile(String name) {
1763dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        byte[] contents = readFile(name);
177261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        return contents != null && contents.length > 0;
178261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
179261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
1803dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private byte[] readFile(String name) {
1813dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        int version;
1823dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        synchronized (mCache) {
1833dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            if (mCache.hasFile(name)) {
1843dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                return mCache.peekFile(name);
1853dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
1863dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            version = mCache.getVersion();
1873dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
1883dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
189261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        RandomAccessFile raf = null;
190261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        byte[] stored = null;
191261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        try {
192261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            raf = new RandomAccessFile(name, "r");
193261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            stored = new byte[(int) raf.length()];
194261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            raf.readFully(stored, 0, stored.length);
195261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            raf.close();
196261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        } catch (IOException e) {
197261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            Slog.e(TAG, "Cannot read file " + e);
198261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        } finally {
199261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            if (raf != null) {
200261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                try {
201261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    raf.close();
202261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                } catch (IOException e) {
203261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    Slog.e(TAG, "Error closing file " + e);
204261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                }
205261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
206261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
2073dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        mCache.putFileIfUnchanged(name, stored, version);
208261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        return stored;
209261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
210261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
2113dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private void writeFile(String name, byte[] hash) {
212261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        synchronized (mFileWriteLock) {
213261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            RandomAccessFile raf = null;
214261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            try {
215261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                // Write the hash to file
216261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                raf = new RandomAccessFile(name, "rw");
217261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                // Truncate the file if pattern is null, to clear the lock
218261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                if (hash == null || hash.length == 0) {
219261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    raf.setLength(0);
220261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                } else {
221261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    raf.write(hash, 0, hash.length);
222261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                }
223261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                raf.close();
224261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            } catch (IOException e) {
225261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                Slog.e(TAG, "Error writing to file " + e);
226261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            } finally {
227261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                if (raf != null) {
228261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    try {
229261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                        raf.close();
230261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    } catch (IOException e) {
231261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                        Slog.e(TAG, "Error closing file " + e);
232261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    }
233261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                }
234261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
2353dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mCache.putFile(name, hash);
236261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
237261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
238261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
239261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    public void writePatternHash(byte[] hash, int userId) {
2403dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        writeFile(getLockPatternFilename(userId), hash);
241261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
242261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
243261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    public void writePasswordHash(byte[] hash, int userId) {
2443dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        writeFile(getLockPasswordFilename(userId), hash);
245261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
246261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
247261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
248e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    @VisibleForTesting
249e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    String getLockPatternFilename(int userId) {
2503dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
251261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
252261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
253e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    @VisibleForTesting
254e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    String getLockPasswordFilename(int userId) {
2553dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
2563dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    }
2573dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
2583dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private String getLockCredentialFilePathForUser(int userId, String basename) {
259261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        userId = getUserParentOrSelfId(userId);
260261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        String dataSystemDirectory =
261261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                android.os.Environment.getDataDirectory().getAbsolutePath() +
262261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                        SYSTEM_DIRECTORY;
263261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        if (userId == 0) {
264261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            // Leave it in the same place for user 0
2653dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return dataSystemDirectory + basename;
266261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        } else {
2673dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
268261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
269261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
270261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
271261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private int getUserParentOrSelfId(int userId) {
272261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        if (userId != 0) {
273261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
274261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            final UserInfo pi = um.getProfileParent(userId);
275261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            if (pi != null) {
276261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                return pi.id;
277261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
278261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
279261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        return userId;
280261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
281261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
282261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
283261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    public void removeUser(int userId) {
284261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
285261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
286261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
287261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        final UserInfo parentInfo = um.getProfileParent(userId);
288261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
289261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        synchronized (mFileWriteLock) {
290261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            if (parentInfo == null) {
291261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                // This user owns its lock settings files - safe to delete them
2923dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                String name = getLockPasswordFilename(userId);
2933dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                File file = new File(name);
294261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                if (file.exists()) {
295261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    file.delete();
2963dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                    mCache.putFile(name, null);
297261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                }
2983dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                name = getLockPatternFilename(userId);
2993dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                file = new File(name);
300261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                if (file.exists()) {
301261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    file.delete();
3023dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                    mCache.putFile(name, null);
303261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                }
304261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
305261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
306261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
307261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        try {
308261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.beginTransaction();
309261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
310261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.setTransactionSuccessful();
3113dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mCache.removeUser(userId);
312261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        } finally {
313261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.endTransaction();
314261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
315261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
316261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
317e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    @VisibleForTesting
318e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    void closeDatabase() {
319e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos        mOpenHelper.close();
320e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    }
321e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos
322e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    @VisibleForTesting
323e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    void clearCache() {
324e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos        mCache.clear();
325e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    }
326261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
3273dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public interface Callback {
328261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        void initialize(SQLiteDatabase db);
329261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
330261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
331261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    class DatabaseHelper extends SQLiteOpenHelper {
332261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        private static final String TAG = "LockSettingsDB";
333261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        private static final String DATABASE_NAME = "locksettings.db";
334261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
335261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        private static final int DATABASE_VERSION = 2;
336261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
337261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        private final Callback mCallback;
338261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
339261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        public DatabaseHelper(Context context, Callback callback) {
340261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            super(context, DATABASE_NAME, null, DATABASE_VERSION);
341261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            setWriteAheadLoggingEnabled(true);
342261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            mCallback = callback;
343261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
344261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
345261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        private void createTable(SQLiteDatabase db) {
346261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.execSQL("CREATE TABLE " + TABLE + " (" +
347261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
348261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    COLUMN_KEY + " TEXT," +
349261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    COLUMN_USERID + " INTEGER," +
350261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    COLUMN_VALUE + " TEXT" +
351261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    ");");
352261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
353261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
354261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        @Override
355261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        public void onCreate(SQLiteDatabase db) {
356261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            createTable(db);
357261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            mCallback.initialize(db);
358261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
359261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
360261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        @Override
361261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
362261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            int upgradeVersion = oldVersion;
363261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            if (upgradeVersion == 1) {
364261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                // Previously migrated lock screen widget settings. Now defunct.
365261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                upgradeVersion = 2;
366261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
367261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
368261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            if (upgradeVersion != DATABASE_VERSION) {
369261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                Log.w(TAG, "Failed to upgrade database!");
370261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
371261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
372261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
3733dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
3743dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    /**
3753dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     * Cache consistency model:
3763dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     * - Writes to storage write directly to the cache, but this MUST happen within the atomic
3773dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *   section either provided by the database transaction or mWriteLock, such that writes to the
3783dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *   cache and writes to the backing storage are guaranteed to occur in the same order
3793dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *
3803dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     * - Reads can populate the cache, but because they are no strong ordering guarantees with
3813dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *   respect to writes this precaution is taken:
3823dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *   - The cache is assigned a version number that increases every time the cache is modified.
3833dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *     Reads from backing storage can only populate the cache if the backing storage
3843dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *     has not changed since the load operation has begun.
3853dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *     This guarantees that no read operation can shadow a write to the cache that happens
3863dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *     after it had begun.
3873dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     */
3883dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private static class Cache {
3893dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
3903dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private final CacheKey mCacheKey = new CacheKey();
3913dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private int mVersion = 0;
3923dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
3933dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        String peekKeyValue(String key, String defaultValue, int userId) {
3943dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
3953dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return cached == DEFAULT ? defaultValue : (String) cached;
3963dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
3973dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
3983dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        boolean hasKeyValue(String key, int userId) {
3993dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
4003dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4013dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4023dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        void putKeyValue(String key, String value, int userId) {
4033dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
4043dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4053dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4063dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
4073dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
4083dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4093dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4103dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        byte[] peekFile(String fileName) {
4113dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
4123dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4133dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4143dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        boolean hasFile(String fileName) {
4153dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
4163dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4173dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4183dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        void putFile(String key, byte[] value) {
4193dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            put(CacheKey.TYPE_FILE, key, value, -1 /* userId */);
4203dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4213dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4223dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        void putFileIfUnchanged(String key, byte[] value, int version) {
4233dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version);
4243dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4253dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4263dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        void setFetched(int userId) {
4273dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
4283dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4293dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4303dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        boolean isFetched(int userId) {
4313dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return contains(CacheKey.TYPE_FETCHED, "", userId);
4323dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4333dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4343dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4353dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private synchronized void put(int type, String key, Object value, int userId) {
4363dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            // Create a new CachKey here because it may be saved in the map if the key is absent.
4373dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mCache.put(new CacheKey().set(type, key, userId), value);
4383dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mVersion++;
4393dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4403dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4413dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
4423dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                int version) {
4433dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            if (!contains(type, key, userId) && mVersion == version) {
4443dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                put(type, key, value, userId);
4453dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
4463dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4473dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4483dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private synchronized boolean contains(int type, String key, int userId) {
4493dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return mCache.containsKey(mCacheKey.set(type, key, userId));
4503dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4513dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4523dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private synchronized Object peek(int type, String key, int userId) {
4533dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return mCache.get(mCacheKey.set(type, key, userId));
4543dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4553dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4563dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private synchronized int getVersion() {
4573dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return mVersion;
4583dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4593dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4603dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        synchronized void removeUser(int userId) {
4613dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            for (int i = mCache.size() - 1; i >= 0; i--) {
4623dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                if (mCache.keyAt(i).userId == userId) {
4633dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                    mCache.removeAt(i);
4643dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                }
4653dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
4663dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4673dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            // Make sure in-flight loads can't write to cache.
4683dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mVersion++;
4693dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
4703dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
471e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos        synchronized void clear() {
472e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos            mCache.clear();
473e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos            mVersion++;
474e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos        }
4753dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4763dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private static final class CacheKey {
4773dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            static final int TYPE_KEY_VALUE = 0;
4783dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            static final int TYPE_FILE = 1;
4793dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            static final int TYPE_FETCHED = 2;
4803dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4813dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            String key;
4823dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            int userId;
4833dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            int type;
4843dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4853dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            public CacheKey set(int type, String key, int userId) {
4863dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                this.type = type;
4873dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                this.key = key;
4883dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                this.userId = userId;
4893dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                return this;
4903dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
4913dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4923dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            @Override
4933dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            public boolean equals(Object obj) {
4943dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                if (!(obj instanceof CacheKey))
4953dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                    return false;
4963dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                CacheKey o = (CacheKey) obj;
4973dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                return userId == o.userId && type == o.type && key.equals(o.key);
4983dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
4993dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5003dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            @Override
5013dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            public int hashCode() {
5023dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                return key.hashCode() ^ userId ^ type;
5033dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
5043dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5053dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    }
506261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos}
507