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/";
59e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales    private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key";
60e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales    private static final String BASE_ZERO_LOCK_PATTERN_FILE = "gatekeeper.gesture.key";
618fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key";
628fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
638fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
64261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
653dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private static final Object DEFAULT = new Object();
663dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
67261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private final DatabaseHelper mOpenHelper;
68261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private final Context mContext;
693dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private final Cache mCache = new Cache();
70261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private final Object mFileWriteLock = new Object();
71261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
728fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    private int mStoredCredentialType;
738fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
748fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    class CredentialHash {
758fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        static final int TYPE_NONE = -1;
768fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        static final int TYPE_PATTERN = 1;
778fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        static final int TYPE_PASSWORD = 2;
788fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
798fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        static final int VERSION_LEGACY = 0;
808fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        static final int VERSION_GATEKEEPER = 1;
818fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
828fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        CredentialHash(byte[] hash, int version) {
838fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            this.hash = hash;
848fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            this.version = version;
85e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales            this.isBaseZeroPattern = false;
86e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales        }
87e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales
88e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales        CredentialHash(byte[] hash, boolean isBaseZeroPattern) {
89e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales            this.hash = hash;
90e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales            this.version = VERSION_GATEKEEPER;
91e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales            this.isBaseZeroPattern = isBaseZeroPattern;
928fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        }
938fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
948fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        byte[] hash;
958fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        int version;
96e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales        boolean isBaseZeroPattern;
978fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    }
988fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
993dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public LockSettingsStorage(Context context, Callback callback) {
100261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        mContext = context;
101261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        mOpenHelper = new DatabaseHelper(context, callback);
102261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
103261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
1043dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public void writeKeyValue(String key, String value, int userId) {
105261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
106261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
107261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
1083dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
109261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        ContentValues cv = new ContentValues();
110261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        cv.put(COLUMN_KEY, key);
111261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        cv.put(COLUMN_USERID, userId);
112261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        cv.put(COLUMN_VALUE, value);
113261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
114261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        db.beginTransaction();
115261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        try {
116261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
117261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    new String[] {key, Integer.toString(userId)});
118261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.insert(TABLE, null, cv);
119261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.setTransactionSuccessful();
1203dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mCache.putKeyValue(key, value, userId);
121261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        } finally {
122261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.endTransaction();
123261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
124261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
125261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
126261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
1273dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public String readKeyValue(String key, String defaultValue, int userId) {
1283dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        int version;
1293dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        synchronized (mCache) {
1303dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            if (mCache.hasKeyValue(key, userId)) {
1313dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                return mCache.peekKeyValue(key, defaultValue, userId);
1323dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
1333dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            version = mCache.getVersion();
1343dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
1353dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
136261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        Cursor cursor;
1373dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        Object result = DEFAULT;
138261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
139261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
140261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
141261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                new String[] { Integer.toString(userId), key },
142261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                null, null, null)) != null) {
143261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            if (cursor.moveToFirst()) {
144261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                result = cursor.getString(0);
145261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
146261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            cursor.close();
147261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
1483dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        mCache.putKeyValueIfUnchanged(key, result, userId, version);
1493dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        return result == DEFAULT ? defaultValue : (String) result;
1503dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    }
1513dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
1523dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public void prefetchUser(int userId) {
1533dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        int version;
1543dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        synchronized (mCache) {
1553dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            if (mCache.isFetched(userId)) {
1563dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                return;
1573dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
1583dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mCache.setFetched(userId);
1593dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            version = mCache.getVersion();
1603dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
1613dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
1623dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        Cursor cursor;
1633dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
1643dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH,
1653dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                COLUMN_USERID + "=?",
1663dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                new String[] { Integer.toString(userId) },
1673dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                null, null, null)) != null) {
1683dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            while (cursor.moveToNext()) {
1693dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                String key = cursor.getString(0);
1703dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                String value = cursor.getString(1);
1713dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                mCache.putKeyValueIfUnchanged(key, value, userId, version);
1723dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
1733dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            cursor.close();
1743dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
1753dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
1763dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        // Populate cache by reading the password and pattern files.
1773dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        readPasswordHash(userId);
1783dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        readPatternHash(userId);
179261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
180261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
1818fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    public int getStoredCredentialType(int userId) {
1828fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        if (mStoredCredentialType != 0) {
1838fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            return mStoredCredentialType;
1848fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        }
1858fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
1868fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        CredentialHash pattern = readPatternHash(userId);
1878fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        if (pattern == null) {
1888fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            if (readPasswordHash(userId) != null) {
1898fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales                mStoredCredentialType = CredentialHash.TYPE_PASSWORD;
1908fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            } else {
1918fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales                mStoredCredentialType = CredentialHash.TYPE_NONE;
1928fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            }
1938fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        } else {
1948fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            CredentialHash password = readPasswordHash(userId);
1958fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            if (password != null) {
1968fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales                // Both will never be GateKeeper
1978fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales                if (password.version == CredentialHash.VERSION_GATEKEEPER) {
1988fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales                    mStoredCredentialType = CredentialHash.TYPE_PASSWORD;
1998fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales                } else {
2008fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales                    mStoredCredentialType = CredentialHash.TYPE_PATTERN;
2018fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales                }
2028fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            } else {
2038fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales                mStoredCredentialType = CredentialHash.TYPE_PATTERN;
2048fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            }
2058fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        }
2068fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
2078fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        return mStoredCredentialType;
2088fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    }
2098fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
2108fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
2118fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    public CredentialHash readPasswordHash(int userId) {
2128fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        byte[] stored = readFile(getLockPasswordFilename(userId));
213261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        if (stored != null && stored.length > 0) {
2148fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER);
215261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
2168fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
2178fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        stored = readFile(getLegacyLockPasswordFilename(userId));
2188fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        if (stored != null && stored.length > 0) {
2198fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            return new CredentialHash(stored, CredentialHash.VERSION_LEGACY);
2208fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        }
2218fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
222261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        return null;
223261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
224261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
2258fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    public CredentialHash readPatternHash(int userId) {
2268fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        byte[] stored = readFile(getLockPatternFilename(userId));
227261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        if (stored != null && stored.length > 0) {
2288fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            return new CredentialHash(stored, CredentialHash.VERSION_GATEKEEPER);
229261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
2308fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
231e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales        stored = readFile(getBaseZeroLockPatternFilename(userId));
232e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales        if (stored != null && stored.length > 0) {
233e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales            return new CredentialHash(stored, true);
234e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales        }
235e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales
2368fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        stored = readFile(getLegacyLockPatternFilename(userId));
2378fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        if (stored != null && stored.length > 0) {
2388fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            return new CredentialHash(stored, CredentialHash.VERSION_LEGACY);
2398fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        }
2408fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
241261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        return null;
242261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
243261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
244e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales
2453dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public boolean hasPassword(int userId) {
2468fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        return hasFile(getLockPasswordFilename(userId)) ||
2478fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            hasFile(getLegacyLockPasswordFilename(userId));
248261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
249261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
2503dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public boolean hasPattern(int userId) {
2518fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        return hasFile(getLockPatternFilename(userId)) ||
252e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales            hasFile(getBaseZeroLockPatternFilename(userId)) ||
2538fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            hasFile(getLegacyLockPatternFilename(userId));
254261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
255261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
2563dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private boolean hasFile(String name) {
2573dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        byte[] contents = readFile(name);
258261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        return contents != null && contents.length > 0;
259261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
260261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
2613dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private byte[] readFile(String name) {
2623dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        int version;
2633dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        synchronized (mCache) {
2643dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            if (mCache.hasFile(name)) {
2653dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                return mCache.peekFile(name);
2663dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
2673dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            version = mCache.getVersion();
2683dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
2693dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
270261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        RandomAccessFile raf = null;
271261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        byte[] stored = null;
272261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        try {
273261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            raf = new RandomAccessFile(name, "r");
274261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            stored = new byte[(int) raf.length()];
275261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            raf.readFully(stored, 0, stored.length);
276261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            raf.close();
277261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        } catch (IOException e) {
278261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            Slog.e(TAG, "Cannot read file " + e);
279261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        } finally {
280261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            if (raf != null) {
281261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                try {
282261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    raf.close();
283261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                } catch (IOException e) {
284261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    Slog.e(TAG, "Error closing file " + e);
285261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                }
286261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
287261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
2883dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        mCache.putFileIfUnchanged(name, stored, version);
289261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        return stored;
290261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
291261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
2923dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private void writeFile(String name, byte[] hash) {
293261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        synchronized (mFileWriteLock) {
294261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            RandomAccessFile raf = null;
295261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            try {
296261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                // Write the hash to file
297261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                raf = new RandomAccessFile(name, "rw");
298261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                // Truncate the file if pattern is null, to clear the lock
299261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                if (hash == null || hash.length == 0) {
300261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    raf.setLength(0);
301261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                } else {
302261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    raf.write(hash, 0, hash.length);
303261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                }
304261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                raf.close();
305261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            } catch (IOException e) {
306261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                Slog.e(TAG, "Error writing to file " + e);
307261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            } finally {
308261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                if (raf != null) {
309261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    try {
310261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                        raf.close();
311261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    } catch (IOException e) {
312261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                        Slog.e(TAG, "Error closing file " + e);
313261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    }
314261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                }
315261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
3163dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mCache.putFile(name, hash);
317261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
318261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
319261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
320e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales    private void deleteFile(String name) {
321e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales        File f = new File(name);
322e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales        if (f != null) {
323e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales            f.delete();
324e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales        }
325e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales    }
326e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales
327261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    public void writePatternHash(byte[] hash, int userId) {
3288fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        mStoredCredentialType = hash == null
3298fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            ? CredentialHash.TYPE_NONE
3308fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            : CredentialHash.TYPE_PATTERN;
3313dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        writeFile(getLockPatternFilename(userId), hash);
33268e4ba4dd0412f7923d3c22c92ebae485fc89961Robin Lee        clearPasswordHash(userId);
33368e4ba4dd0412f7923d3c22c92ebae485fc89961Robin Lee    }
33468e4ba4dd0412f7923d3c22c92ebae485fc89961Robin Lee
33568e4ba4dd0412f7923d3c22c92ebae485fc89961Robin Lee    private void clearPatternHash(int userId) {
33668e4ba4dd0412f7923d3c22c92ebae485fc89961Robin Lee        writeFile(getLockPatternFilename(userId), null);
337261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
338261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
339261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    public void writePasswordHash(byte[] hash, int userId) {
3408fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        mStoredCredentialType = hash == null
3418fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            ? CredentialHash.TYPE_NONE
3428fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales            : CredentialHash.TYPE_PASSWORD;
3433dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        writeFile(getLockPasswordFilename(userId), hash);
34468e4ba4dd0412f7923d3c22c92ebae485fc89961Robin Lee        clearPatternHash(userId);
345261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
346261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
34768e4ba4dd0412f7923d3c22c92ebae485fc89961Robin Lee    private void clearPasswordHash(int userId) {
34868e4ba4dd0412f7923d3c22c92ebae485fc89961Robin Lee        writeFile(getLockPasswordFilename(userId), null);
34968e4ba4dd0412f7923d3c22c92ebae485fc89961Robin Lee    }
350261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
351e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    @VisibleForTesting
352e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    String getLockPatternFilename(int userId) {
3533dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
354261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
355261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
356e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    @VisibleForTesting
357e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    String getLockPasswordFilename(int userId) {
3583dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
3593dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    }
3603dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
3618fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    @VisibleForTesting
3628fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    String getLegacyLockPatternFilename(int userId) {
3638fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE);
3648fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    }
3658fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
3668fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    @VisibleForTesting
3678fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    String getLegacyLockPasswordFilename(int userId) {
3688fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales        return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE);
3698fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales    }
3708fa5665f0e757cec0063fb4cf1354f1596f93a91Andres Morales
371e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales    private String getBaseZeroLockPatternFilename(int userId) {
372e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales        return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE);
373e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales    }
374e40bad8cf9397becdf05776c775c8286d3de46faAndres Morales
3753dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private String getLockCredentialFilePathForUser(int userId, String basename) {
376261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        userId = getUserParentOrSelfId(userId);
377261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        String dataSystemDirectory =
378261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                android.os.Environment.getDataDirectory().getAbsolutePath() +
379261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                        SYSTEM_DIRECTORY;
380261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        if (userId == 0) {
381261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            // Leave it in the same place for user 0
3823dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return dataSystemDirectory + basename;
383261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        } else {
3843dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
385261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
386261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
387261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
388261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    private int getUserParentOrSelfId(int userId) {
389261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        if (userId != 0) {
390261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
391261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            final UserInfo pi = um.getProfileParent(userId);
392261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            if (pi != null) {
393261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                return pi.id;
394261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
395261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
396261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        return userId;
397261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
398261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
399261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    public void removeUser(int userId) {
400261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
401261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
402261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
403261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        final UserInfo parentInfo = um.getProfileParent(userId);
404261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
40568e4ba4dd0412f7923d3c22c92ebae485fc89961Robin Lee        if (parentInfo == null) {
40668e4ba4dd0412f7923d3c22c92ebae485fc89961Robin Lee            // This user owns its lock settings files - safe to delete them
40768e4ba4dd0412f7923d3c22c92ebae485fc89961Robin Lee            synchronized (mFileWriteLock) {
4083dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                String name = getLockPasswordFilename(userId);
4093dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                File file = new File(name);
410261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                if (file.exists()) {
411261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    file.delete();
4123dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                    mCache.putFile(name, null);
413261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                }
4143dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                name = getLockPatternFilename(userId);
4153dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                file = new File(name);
416261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                if (file.exists()) {
417261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    file.delete();
4183dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                    mCache.putFile(name, null);
419261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                }
420261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
421261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
422261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
423261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        try {
424261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.beginTransaction();
425261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
426261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.setTransactionSuccessful();
4273dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mCache.removeUser(userId);
428261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        } finally {
429261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.endTransaction();
430261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
431261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
432261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
433e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    @VisibleForTesting
434e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    void closeDatabase() {
435e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos        mOpenHelper.close();
436e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    }
437e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos
438e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    @VisibleForTesting
439e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    void clearCache() {
440e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos        mCache.clear();
441e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos    }
442261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
4433dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    public interface Callback {
444261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        void initialize(SQLiteDatabase db);
445261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
446261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
447261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    class DatabaseHelper extends SQLiteOpenHelper {
448261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        private static final String TAG = "LockSettingsDB";
449261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        private static final String DATABASE_NAME = "locksettings.db";
450261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
451261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        private static final int DATABASE_VERSION = 2;
452261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
453261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        private final Callback mCallback;
454261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
455261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        public DatabaseHelper(Context context, Callback callback) {
456261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            super(context, DATABASE_NAME, null, DATABASE_VERSION);
457261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            setWriteAheadLoggingEnabled(true);
458261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            mCallback = callback;
459261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
460261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
461261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        private void createTable(SQLiteDatabase db) {
462261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            db.execSQL("CREATE TABLE " + TABLE + " (" +
463261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
464261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    COLUMN_KEY + " TEXT," +
465261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    COLUMN_USERID + " INTEGER," +
466261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    COLUMN_VALUE + " TEXT" +
467261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                    ");");
468261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
469261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
470261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        @Override
471261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        public void onCreate(SQLiteDatabase db) {
472261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            createTable(db);
473261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            mCallback.initialize(db);
474261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
475261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
476261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        @Override
477261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
478261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            int upgradeVersion = oldVersion;
479261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            if (upgradeVersion == 1) {
480261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                // Previously migrated lock screen widget settings. Now defunct.
481261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                upgradeVersion = 2;
482261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
483261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos
484261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            if (upgradeVersion != DATABASE_VERSION) {
485261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos                Log.w(TAG, "Failed to upgrade database!");
486261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos            }
487261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos        }
488261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos    }
4893dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
4903dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    /**
4913dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     * Cache consistency model:
4923dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     * - Writes to storage write directly to the cache, but this MUST happen within the atomic
4933dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *   section either provided by the database transaction or mWriteLock, such that writes to the
4943dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *   cache and writes to the backing storage are guaranteed to occur in the same order
4953dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *
4963dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     * - Reads can populate the cache, but because they are no strong ordering guarantees with
4973dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *   respect to writes this precaution is taken:
4983dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *   - The cache is assigned a version number that increases every time the cache is modified.
4993dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *     Reads from backing storage can only populate the cache if the backing storage
5003dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *     has not changed since the load operation has begun.
5013dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *     This guarantees that no read operation can shadow a write to the cache that happens
5023dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     *     after it had begun.
5033dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos     */
5043dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    private static class Cache {
5053dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
5063dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private final CacheKey mCacheKey = new CacheKey();
5073dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private int mVersion = 0;
5083dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5093dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        String peekKeyValue(String key, String defaultValue, int userId) {
5103dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
5113dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return cached == DEFAULT ? defaultValue : (String) cached;
5123dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5133dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5143dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        boolean hasKeyValue(String key, int userId) {
5153dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
5163dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5173dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5183dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        void putKeyValue(String key, String value, int userId) {
5193dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
5203dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5213dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5223dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
5233dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
5243dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5253dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5263dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        byte[] peekFile(String fileName) {
5273dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
5283dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5293dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5303dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        boolean hasFile(String fileName) {
5313dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
5323dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5333dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5343dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        void putFile(String key, byte[] value) {
5353dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            put(CacheKey.TYPE_FILE, key, value, -1 /* userId */);
5363dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5373dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5383dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        void putFileIfUnchanged(String key, byte[] value, int version) {
5393dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version);
5403dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5413dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5423dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        void setFetched(int userId) {
5433dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
5443dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5453dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5463dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        boolean isFetched(int userId) {
5473dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return contains(CacheKey.TYPE_FETCHED, "", userId);
5483dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5493dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5503dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5513dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private synchronized void put(int type, String key, Object value, int userId) {
5523dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            // Create a new CachKey here because it may be saved in the map if the key is absent.
5533dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mCache.put(new CacheKey().set(type, key, userId), value);
5543dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mVersion++;
5553dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5563dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5573dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
5583dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                int version) {
5593dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            if (!contains(type, key, userId) && mVersion == version) {
5603dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                put(type, key, value, userId);
5613dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
5623dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5633dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5643dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private synchronized boolean contains(int type, String key, int userId) {
5653dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return mCache.containsKey(mCacheKey.set(type, key, userId));
5663dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5673dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5683dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private synchronized Object peek(int type, String key, int userId) {
5693dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return mCache.get(mCacheKey.set(type, key, userId));
5703dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5713dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5723dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private synchronized int getVersion() {
5733dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            return mVersion;
5743dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5753dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5763dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        synchronized void removeUser(int userId) {
5773dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            for (int i = mCache.size() - 1; i >= 0; i--) {
5783dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                if (mCache.keyAt(i).userId == userId) {
5793dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                    mCache.removeAt(i);
5803dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                }
5813dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
5823dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5833dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            // Make sure in-flight loads can't write to cache.
5843dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            mVersion++;
5853dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
5863dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
587e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos        synchronized void clear() {
588e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos            mCache.clear();
589e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos            mVersion++;
590e542499a304f067372d85722e11a74b4e56b0bd7Adrian Roos        }
5913dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5923dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        private static final class CacheKey {
5933dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            static final int TYPE_KEY_VALUE = 0;
5943dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            static final int TYPE_FILE = 1;
5953dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            static final int TYPE_FETCHED = 2;
5963dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
5973dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            String key;
5983dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            int userId;
5993dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            int type;
6003dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
6013dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            public CacheKey set(int type, String key, int userId) {
6023dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                this.type = type;
6033dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                this.key = key;
6043dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                this.userId = userId;
6053dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                return this;
6063dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
6073dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
6083dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            @Override
6093dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            public boolean equals(Object obj) {
6103dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                if (!(obj instanceof CacheKey))
6113dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                    return false;
6123dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                CacheKey o = (CacheKey) obj;
6133dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                return userId == o.userId && type == o.type && key.equals(o.key);
6143dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
6153dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos
6163dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            @Override
6173dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            public int hashCode() {
6183dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos                return key.hashCode() ^ userId ^ type;
6193dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos            }
6203dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos        }
6213dcae68501a1fc1c433d12a9d55a31c7eaab016cAdrian Roos    }
622261d5ab8f4c3fdd34163468fd48ab07f7ad13d3cAdrian Roos}
623