Home | History | Annotate | Download | only in locksettings
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.server.locksettings;
     18 
     19 import static android.content.Context.USER_SERVICE;
     20 
     21 import android.annotation.Nullable;
     22 import android.app.admin.DevicePolicyManager;
     23 import android.content.ContentValues;
     24 import android.content.Context;
     25 import android.content.pm.UserInfo;
     26 import android.database.Cursor;
     27 import android.database.sqlite.SQLiteDatabase;
     28 import android.database.sqlite.SQLiteOpenHelper;
     29 import android.os.Environment;
     30 import android.os.UserHandle;
     31 import android.os.UserManager;
     32 import android.os.storage.StorageManager;
     33 import android.util.ArrayMap;
     34 import android.util.Log;
     35 import android.util.Slog;
     36 
     37 import com.android.internal.annotations.VisibleForTesting;
     38 import com.android.internal.util.ArrayUtils;
     39 import com.android.internal.util.Preconditions;
     40 import com.android.internal.widget.LockPatternUtils;
     41 import com.android.server.LocalServices;
     42 import com.android.server.PersistentDataBlockManagerInternal;
     43 
     44 import java.io.ByteArrayInputStream;
     45 import java.io.ByteArrayOutputStream;
     46 import java.io.DataInputStream;
     47 import java.io.DataOutputStream;
     48 import java.io.File;
     49 import java.io.IOException;
     50 import java.io.RandomAccessFile;
     51 import java.util.ArrayList;
     52 import java.util.List;
     53 import java.util.Map;
     54 
     55 /**
     56  * Storage for the lock settings service.
     57  */
     58 class LockSettingsStorage {
     59 
     60     private static final String TAG = "LockSettingsStorage";
     61     private static final String TABLE = "locksettings";
     62     private static final boolean DEBUG = false;
     63 
     64     private static final String COLUMN_KEY = "name";
     65     private static final String COLUMN_USERID = "user";
     66     private static final String COLUMN_VALUE = "value";
     67 
     68     private static final String[] COLUMNS_FOR_QUERY = {
     69             COLUMN_VALUE
     70     };
     71     private static final String[] COLUMNS_FOR_PREFETCH = {
     72             COLUMN_KEY, COLUMN_VALUE
     73     };
     74 
     75     private static final String SYSTEM_DIRECTORY = "/system/";
     76     private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key";
     77     private static final String BASE_ZERO_LOCK_PATTERN_FILE = "gatekeeper.gesture.key";
     78     private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key";
     79     private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
     80     private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
     81     private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
     82 
     83     private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
     84 
     85     private static final Object DEFAULT = new Object();
     86 
     87     private final DatabaseHelper mOpenHelper;
     88     private final Context mContext;
     89     private final Cache mCache = new Cache();
     90     private final Object mFileWriteLock = new Object();
     91 
     92     private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal;
     93 
     94     @VisibleForTesting
     95     public static class CredentialHash {
     96         static final int VERSION_LEGACY = 0;
     97         static final int VERSION_GATEKEEPER = 1;
     98 
     99         private CredentialHash(byte[] hash, int type, int version) {
    100             this(hash, type, version, false /* isBaseZeroPattern */);
    101         }
    102 
    103         private CredentialHash(byte[] hash, int type, int version, boolean isBaseZeroPattern) {
    104             if (type != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
    105                 if (hash == null) {
    106                     throw new RuntimeException("Empty hash for CredentialHash");
    107                 }
    108             } else /* type == LockPatternUtils.CREDENTIAL_TYPE_NONE */ {
    109                 if (hash != null) {
    110                     throw new RuntimeException("None type CredentialHash should not have hash");
    111                 }
    112             }
    113             this.hash = hash;
    114             this.type = type;
    115             this.version = version;
    116             this.isBaseZeroPattern = isBaseZeroPattern;
    117         }
    118 
    119         private static CredentialHash createBaseZeroPattern(byte[] hash) {
    120             return new CredentialHash(hash, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
    121                     VERSION_GATEKEEPER, true /* isBaseZeroPattern */);
    122         }
    123 
    124         static CredentialHash create(byte[] hash, int type) {
    125             if (type == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
    126                 throw new RuntimeException("Bad type for CredentialHash");
    127             }
    128             return new CredentialHash(hash, type, VERSION_GATEKEEPER);
    129         }
    130 
    131         static CredentialHash createEmptyHash() {
    132             return new CredentialHash(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
    133                     VERSION_GATEKEEPER);
    134         }
    135 
    136         byte[] hash;
    137         int type;
    138         int version;
    139         boolean isBaseZeroPattern;
    140 
    141         public byte[] toBytes() {
    142             Preconditions.checkState(!isBaseZeroPattern, "base zero patterns are not serializable");
    143 
    144             try {
    145                 ByteArrayOutputStream os = new ByteArrayOutputStream();
    146                 DataOutputStream dos = new DataOutputStream(os);
    147                 dos.write(version);
    148                 dos.write(type);
    149                 if (hash != null && hash.length > 0) {
    150                     dos.writeInt(hash.length);
    151                     dos.write(hash);
    152                 } else {
    153                     dos.writeInt(0);
    154                 }
    155                 dos.close();
    156                 return os.toByteArray();
    157             } catch (IOException e) {
    158                 throw new RuntimeException(e);
    159             }
    160         }
    161 
    162         public static CredentialHash fromBytes(byte[] bytes) {
    163             try {
    164                 DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
    165                 int version = is.read();
    166                 int type = is.read();
    167                 int hashSize = is.readInt();
    168                 byte[] hash = null;
    169                 if (hashSize > 0) {
    170                     hash = new byte[hashSize];
    171                     is.readFully(hash);
    172                 }
    173                 return new CredentialHash(hash, type, version);
    174             } catch (IOException e) {
    175                 throw new RuntimeException(e);
    176             }
    177         }
    178     }
    179 
    180     public LockSettingsStorage(Context context) {
    181         mContext = context;
    182         mOpenHelper = new DatabaseHelper(context);
    183     }
    184 
    185     public void setDatabaseOnCreateCallback(Callback callback) {
    186         mOpenHelper.setCallback(callback);
    187     }
    188 
    189     public void writeKeyValue(String key, String value, int userId) {
    190         writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId);
    191     }
    192 
    193     public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) {
    194         ContentValues cv = new ContentValues();
    195         cv.put(COLUMN_KEY, key);
    196         cv.put(COLUMN_USERID, userId);
    197         cv.put(COLUMN_VALUE, value);
    198 
    199         db.beginTransaction();
    200         try {
    201             db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
    202                     new String[] {key, Integer.toString(userId)});
    203             db.insert(TABLE, null, cv);
    204             db.setTransactionSuccessful();
    205             mCache.putKeyValue(key, value, userId);
    206         } finally {
    207             db.endTransaction();
    208         }
    209 
    210     }
    211 
    212     public String readKeyValue(String key, String defaultValue, int userId) {
    213         int version;
    214         synchronized (mCache) {
    215             if (mCache.hasKeyValue(key, userId)) {
    216                 return mCache.peekKeyValue(key, defaultValue, userId);
    217             }
    218             version = mCache.getVersion();
    219         }
    220 
    221         Cursor cursor;
    222         Object result = DEFAULT;
    223         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    224         if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
    225                 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
    226                 new String[] { Integer.toString(userId), key },
    227                 null, null, null)) != null) {
    228             if (cursor.moveToFirst()) {
    229                 result = cursor.getString(0);
    230             }
    231             cursor.close();
    232         }
    233         mCache.putKeyValueIfUnchanged(key, result, userId, version);
    234         return result == DEFAULT ? defaultValue : (String) result;
    235     }
    236 
    237     public void prefetchUser(int userId) {
    238         int version;
    239         synchronized (mCache) {
    240             if (mCache.isFetched(userId)) {
    241                 return;
    242             }
    243             mCache.setFetched(userId);
    244             version = mCache.getVersion();
    245         }
    246 
    247         Cursor cursor;
    248         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    249         if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH,
    250                 COLUMN_USERID + "=?",
    251                 new String[] { Integer.toString(userId) },
    252                 null, null, null)) != null) {
    253             while (cursor.moveToNext()) {
    254                 String key = cursor.getString(0);
    255                 String value = cursor.getString(1);
    256                 mCache.putKeyValueIfUnchanged(key, value, userId, version);
    257             }
    258             cursor.close();
    259         }
    260 
    261         // Populate cache by reading the password and pattern files.
    262         readCredentialHash(userId);
    263     }
    264 
    265     private CredentialHash readPasswordHashIfExists(int userId) {
    266         byte[] stored = readFile(getLockPasswordFilename(userId));
    267         if (!ArrayUtils.isEmpty(stored)) {
    268             return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
    269                     CredentialHash.VERSION_GATEKEEPER);
    270         }
    271 
    272         stored = readFile(getLegacyLockPasswordFilename(userId));
    273         if (!ArrayUtils.isEmpty(stored)) {
    274             return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
    275                     CredentialHash.VERSION_LEGACY);
    276         }
    277         return null;
    278     }
    279 
    280     private CredentialHash readPatternHashIfExists(int userId) {
    281         byte[] stored = readFile(getLockPatternFilename(userId));
    282         if (!ArrayUtils.isEmpty(stored)) {
    283             return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
    284                     CredentialHash.VERSION_GATEKEEPER);
    285         }
    286 
    287         stored = readFile(getBaseZeroLockPatternFilename(userId));
    288         if (!ArrayUtils.isEmpty(stored)) {
    289             return CredentialHash.createBaseZeroPattern(stored);
    290         }
    291 
    292         stored = readFile(getLegacyLockPatternFilename(userId));
    293         if (!ArrayUtils.isEmpty(stored)) {
    294             return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
    295                     CredentialHash.VERSION_LEGACY);
    296         }
    297         return null;
    298     }
    299 
    300     public CredentialHash readCredentialHash(int userId) {
    301         CredentialHash passwordHash = readPasswordHashIfExists(userId);
    302         CredentialHash patternHash = readPatternHashIfExists(userId);
    303         if (passwordHash != null && patternHash != null) {
    304             if (passwordHash.version == CredentialHash.VERSION_GATEKEEPER) {
    305                 return passwordHash;
    306             } else {
    307                 return patternHash;
    308             }
    309         } else if (passwordHash != null) {
    310             return passwordHash;
    311         } else if (patternHash != null) {
    312             return patternHash;
    313         } else {
    314             return CredentialHash.createEmptyHash();
    315         }
    316     }
    317 
    318     public void removeChildProfileLock(int userId) {
    319         if (DEBUG)
    320             Slog.e(TAG, "Remove child profile lock for user: " + userId);
    321         try {
    322             deleteFile(getChildProfileLockFile(userId));
    323         } catch (Exception e) {
    324             e.printStackTrace();
    325         }
    326     }
    327 
    328     public void writeChildProfileLock(int userId, byte[] lock) {
    329         writeFile(getChildProfileLockFile(userId), lock);
    330     }
    331 
    332     public byte[] readChildProfileLock(int userId) {
    333         return readFile(getChildProfileLockFile(userId));
    334     }
    335 
    336     public boolean hasChildProfileLock(int userId) {
    337         return hasFile(getChildProfileLockFile(userId));
    338     }
    339 
    340     public boolean hasPassword(int userId) {
    341         return hasFile(getLockPasswordFilename(userId)) ||
    342             hasFile(getLegacyLockPasswordFilename(userId));
    343     }
    344 
    345     public boolean hasPattern(int userId) {
    346         return hasFile(getLockPatternFilename(userId)) ||
    347             hasFile(getBaseZeroLockPatternFilename(userId)) ||
    348             hasFile(getLegacyLockPatternFilename(userId));
    349     }
    350 
    351     public boolean hasCredential(int userId) {
    352         return hasPassword(userId) || hasPattern(userId);
    353     }
    354 
    355     private boolean hasFile(String name) {
    356         byte[] contents = readFile(name);
    357         return contents != null && contents.length > 0;
    358     }
    359 
    360     private byte[] readFile(String name) {
    361         int version;
    362         synchronized (mCache) {
    363             if (mCache.hasFile(name)) {
    364                 return mCache.peekFile(name);
    365             }
    366             version = mCache.getVersion();
    367         }
    368 
    369         RandomAccessFile raf = null;
    370         byte[] stored = null;
    371         try {
    372             raf = new RandomAccessFile(name, "r");
    373             stored = new byte[(int) raf.length()];
    374             raf.readFully(stored, 0, stored.length);
    375             raf.close();
    376         } catch (IOException e) {
    377             Slog.e(TAG, "Cannot read file " + e);
    378         } finally {
    379             if (raf != null) {
    380                 try {
    381                     raf.close();
    382                 } catch (IOException e) {
    383                     Slog.e(TAG, "Error closing file " + e);
    384                 }
    385             }
    386         }
    387         mCache.putFileIfUnchanged(name, stored, version);
    388         return stored;
    389     }
    390 
    391     private void writeFile(String name, byte[] hash) {
    392         synchronized (mFileWriteLock) {
    393             RandomAccessFile raf = null;
    394             try {
    395                 // Write the hash to file, requiring each write to be synchronized to the
    396                 // underlying storage device immediately to avoid data loss in case of power loss.
    397                 // This also ensures future secdiscard operation on the file succeeds since the
    398                 // file would have been allocated on flash.
    399                 raf = new RandomAccessFile(name, "rws");
    400                 // Truncate the file if pattern is null, to clear the lock
    401                 if (hash == null || hash.length == 0) {
    402                     raf.setLength(0);
    403                 } else {
    404                     raf.write(hash, 0, hash.length);
    405                 }
    406                 raf.close();
    407             } catch (IOException e) {
    408                 Slog.e(TAG, "Error writing to file " + e);
    409             } finally {
    410                 if (raf != null) {
    411                     try {
    412                         raf.close();
    413                     } catch (IOException e) {
    414                         Slog.e(TAG, "Error closing file " + e);
    415                     }
    416                 }
    417             }
    418             mCache.putFile(name, hash);
    419         }
    420     }
    421 
    422     private void deleteFile(String name) {
    423         if (DEBUG) Slog.e(TAG, "Delete file " + name);
    424         synchronized (mFileWriteLock) {
    425             File file = new File(name);
    426             if (file.exists()) {
    427                 file.delete();
    428                 mCache.putFile(name, null);
    429             }
    430         }
    431     }
    432 
    433     public void writeCredentialHash(CredentialHash hash, int userId) {
    434         byte[] patternHash = null;
    435         byte[] passwordHash = null;
    436 
    437         if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) {
    438             passwordHash = hash.hash;
    439         } else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
    440             patternHash = hash.hash;
    441         }
    442         writeFile(getLockPasswordFilename(userId), passwordHash);
    443         writeFile(getLockPatternFilename(userId), patternHash);
    444     }
    445 
    446     @VisibleForTesting
    447     String getLockPatternFilename(int userId) {
    448         return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
    449     }
    450 
    451     @VisibleForTesting
    452     String getLockPasswordFilename(int userId) {
    453         return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
    454     }
    455 
    456     @VisibleForTesting
    457     String getLegacyLockPatternFilename(int userId) {
    458         return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE);
    459     }
    460 
    461     @VisibleForTesting
    462     String getLegacyLockPasswordFilename(int userId) {
    463         return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE);
    464     }
    465 
    466     private String getBaseZeroLockPatternFilename(int userId) {
    467         return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE);
    468     }
    469 
    470     @VisibleForTesting
    471     String getChildProfileLockFile(int userId) {
    472         return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
    473     }
    474 
    475     private String getLockCredentialFilePathForUser(int userId, String basename) {
    476         String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() +
    477                         SYSTEM_DIRECTORY;
    478         if (userId == 0) {
    479             // Leave it in the same place for user 0
    480             return dataSystemDirectory + basename;
    481         } else {
    482             return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath();
    483         }
    484     }
    485 
    486     public void writeSyntheticPasswordState(int userId, long handle, String name, byte[] data) {
    487         ensureSyntheticPasswordDirectoryForUser(userId);
    488         writeFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name), data);
    489     }
    490 
    491     public byte[] readSyntheticPasswordState(int userId, long handle, String name) {
    492         return readFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name));
    493     }
    494 
    495     public void deleteSyntheticPasswordState(int userId, long handle, String name) {
    496         String path = getSynthenticPasswordStateFilePathForUser(userId, handle, name);
    497         File file = new File(path);
    498         if (file.exists()) {
    499             try (RandomAccessFile raf = new RandomAccessFile(path, "rws")) {
    500                 final int fileSize = (int) raf.length();
    501                 raf.write(new byte[fileSize]);
    502             } catch (Exception e) {
    503                 Slog.w(TAG, "Failed to zeroize " + path, e);
    504             } finally {
    505                 file.delete();
    506             }
    507             mCache.putFile(path, null);
    508         }
    509     }
    510 
    511     public Map<Integer, List<Long>> listSyntheticPasswordHandlesForAllUsers(String stateName) {
    512         Map<Integer, List<Long>> result = new ArrayMap<>();
    513         final UserManager um = UserManager.get(mContext);
    514         for (UserInfo user : um.getUsers(false)) {
    515             result.put(user.id, listSyntheticPasswordHandlesForUser(stateName, user.id));
    516         }
    517         return result;
    518     }
    519 
    520     public List<Long> listSyntheticPasswordHandlesForUser(String stateName, int userId) {
    521         File baseDir = getSyntheticPasswordDirectoryForUser(userId);
    522         List<Long> result = new ArrayList<>();
    523         File[] files = baseDir.listFiles();
    524         if (files == null) {
    525             return result;
    526         }
    527         for (File file : files) {
    528             String[] parts = file.getName().split("\\.");
    529             if (parts.length == 2 && parts[1].equals(stateName)) {
    530                 try {
    531                     result.add(Long.parseUnsignedLong(parts[0], 16));
    532                 } catch (NumberFormatException e) {
    533                     Slog.e(TAG, "Failed to parse handle " + parts[0]);
    534                 }
    535             }
    536         }
    537         return result;
    538     }
    539 
    540     @VisibleForTesting
    541     protected File getSyntheticPasswordDirectoryForUser(int userId) {
    542         return new File(Environment.getDataSystemDeDirectory(userId) ,SYNTHETIC_PASSWORD_DIRECTORY);
    543     }
    544 
    545     /** Ensure per-user directory for synthetic password state exists */
    546     private void ensureSyntheticPasswordDirectoryForUser(int userId) {
    547         File baseDir = getSyntheticPasswordDirectoryForUser(userId);
    548         if (!baseDir.exists()) {
    549             baseDir.mkdir();
    550         }
    551     }
    552 
    553     @VisibleForTesting
    554     protected String getSynthenticPasswordStateFilePathForUser(int userId, long handle,
    555             String name) {
    556         final File baseDir = getSyntheticPasswordDirectoryForUser(userId);
    557         final String baseName = String.format("%016x.%s", handle, name);
    558         return new File(baseDir, baseName).getAbsolutePath();
    559     }
    560 
    561     public void removeUser(int userId) {
    562         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    563 
    564         final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
    565         final UserInfo parentInfo = um.getProfileParent(userId);
    566 
    567         if (parentInfo == null) {
    568             // This user owns its lock settings files - safe to delete them
    569             synchronized (mFileWriteLock) {
    570                 String name = getLockPasswordFilename(userId);
    571                 File file = new File(name);
    572                 if (file.exists()) {
    573                     file.delete();
    574                     mCache.putFile(name, null);
    575                 }
    576                 name = getLockPatternFilename(userId);
    577                 file = new File(name);
    578                 if (file.exists()) {
    579                     file.delete();
    580                     mCache.putFile(name, null);
    581                 }
    582             }
    583         } else {
    584             // Managed profile
    585             removeChildProfileLock(userId);
    586         }
    587 
    588         File spStateDir = getSyntheticPasswordDirectoryForUser(userId);
    589         try {
    590             db.beginTransaction();
    591             db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
    592             db.setTransactionSuccessful();
    593             mCache.removeUser(userId);
    594             // The directory itself will be deleted as part of user deletion operation by the
    595             // framework, so only need to purge cache here.
    596             //TODO: (b/34600579) invoke secdiscardable
    597             mCache.purgePath(spStateDir.getAbsolutePath());
    598         } finally {
    599             db.endTransaction();
    600         }
    601     }
    602 
    603     @VisibleForTesting
    604     void closeDatabase() {
    605         mOpenHelper.close();
    606     }
    607 
    608     @VisibleForTesting
    609     void clearCache() {
    610         mCache.clear();
    611     }
    612 
    613     @Nullable
    614     public PersistentDataBlockManagerInternal getPersistentDataBlock() {
    615         if (mPersistentDataBlockManagerInternal == null) {
    616             mPersistentDataBlockManagerInternal =
    617                     LocalServices.getService(PersistentDataBlockManagerInternal.class);
    618         }
    619         return mPersistentDataBlockManagerInternal;
    620     }
    621 
    622     public void writePersistentDataBlock(int persistentType, int userId, int qualityForUi,
    623             byte[] payload) {
    624         PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlock();
    625         if (persistentDataBlock == null) {
    626             return;
    627         }
    628         persistentDataBlock.setFrpCredentialHandle(PersistentData.toBytes(
    629                 persistentType, userId, qualityForUi, payload));
    630     }
    631 
    632     public PersistentData readPersistentDataBlock() {
    633         PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlock();
    634         if (persistentDataBlock == null) {
    635             return PersistentData.NONE;
    636         }
    637         try {
    638             return PersistentData.fromBytes(persistentDataBlock.getFrpCredentialHandle());
    639         } catch (IllegalStateException e) {
    640             Slog.e(TAG, "Error reading persistent data block", e);
    641             return PersistentData.NONE;
    642         }
    643     }
    644 
    645     public static class PersistentData {
    646         static final byte VERSION_1 = 1;
    647         static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4;
    648 
    649         public static final int TYPE_NONE = 0;
    650         public static final int TYPE_SP = 1;
    651         public static final int TYPE_SP_WEAVER = 2;
    652 
    653         public static final PersistentData NONE = new PersistentData(TYPE_NONE,
    654                 UserHandle.USER_NULL, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, null);
    655 
    656         final int type;
    657         final int userId;
    658         final int qualityForUi;
    659         final byte[] payload;
    660 
    661         private PersistentData(int type, int userId, int qualityForUi, byte[] payload) {
    662             this.type = type;
    663             this.userId = userId;
    664             this.qualityForUi = qualityForUi;
    665             this.payload = payload;
    666         }
    667 
    668         public static PersistentData fromBytes(byte[] frpData) {
    669             if (frpData == null || frpData.length == 0) {
    670                 return NONE;
    671             }
    672 
    673             DataInputStream is = new DataInputStream(new ByteArrayInputStream(frpData));
    674             try {
    675                 byte version = is.readByte();
    676                 if (version == PersistentData.VERSION_1) {
    677                     int type = is.readByte() & 0xFF;
    678                     int userId = is.readInt();
    679                     int qualityForUi = is.readInt();
    680                     byte[] payload = new byte[frpData.length - VERSION_1_HEADER_SIZE];
    681                     System.arraycopy(frpData, VERSION_1_HEADER_SIZE, payload, 0, payload.length);
    682                     return new PersistentData(type, userId, qualityForUi, payload);
    683                 } else {
    684                     Slog.wtf(TAG, "Unknown PersistentData version code: " + version);
    685                     return NONE;
    686                 }
    687             } catch (IOException e) {
    688                 Slog.wtf(TAG, "Could not parse PersistentData", e);
    689                 return NONE;
    690             }
    691         }
    692 
    693         public static byte[] toBytes(int persistentType, int userId, int qualityForUi,
    694                 byte[] payload) {
    695             if (persistentType == PersistentData.TYPE_NONE) {
    696                 Preconditions.checkArgument(payload == null,
    697                         "TYPE_NONE must have empty payload");
    698                 return null;
    699             }
    700             Preconditions.checkArgument(payload != null && payload.length > 0,
    701                     "empty payload must only be used with TYPE_NONE");
    702 
    703             ByteArrayOutputStream os = new ByteArrayOutputStream(
    704                     VERSION_1_HEADER_SIZE + payload.length);
    705             DataOutputStream dos = new DataOutputStream(os);
    706             try {
    707                 dos.writeByte(PersistentData.VERSION_1);
    708                 dos.writeByte(persistentType);
    709                 dos.writeInt(userId);
    710                 dos.writeInt(qualityForUi);
    711                 dos.write(payload);
    712             } catch (IOException e) {
    713                 throw new RuntimeException("ByteArrayOutputStream cannot throw IOException");
    714             }
    715             return os.toByteArray();
    716         }
    717     }
    718 
    719     public interface Callback {
    720         void initialize(SQLiteDatabase db);
    721     }
    722 
    723     static class DatabaseHelper extends SQLiteOpenHelper {
    724         private static final String TAG = "LockSettingsDB";
    725         private static final String DATABASE_NAME = "locksettings.db";
    726 
    727         private static final int DATABASE_VERSION = 2;
    728         private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000;
    729 
    730         private Callback mCallback;
    731 
    732         public DatabaseHelper(Context context) {
    733             super(context, DATABASE_NAME, null, DATABASE_VERSION);
    734             setWriteAheadLoggingEnabled(true);
    735             // Memory optimization - close idle connections after 30s of inactivity
    736             setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
    737         }
    738 
    739         public void setCallback(Callback callback) {
    740             mCallback = callback;
    741         }
    742 
    743         private void createTable(SQLiteDatabase db) {
    744             db.execSQL("CREATE TABLE " + TABLE + " (" +
    745                     "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
    746                     COLUMN_KEY + " TEXT," +
    747                     COLUMN_USERID + " INTEGER," +
    748                     COLUMN_VALUE + " TEXT" +
    749                     ");");
    750         }
    751 
    752         @Override
    753         public void onCreate(SQLiteDatabase db) {
    754             createTable(db);
    755             if (mCallback != null) {
    756                 mCallback.initialize(db);
    757             }
    758         }
    759 
    760         @Override
    761         public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
    762             int upgradeVersion = oldVersion;
    763             if (upgradeVersion == 1) {
    764                 // Previously migrated lock screen widget settings. Now defunct.
    765                 upgradeVersion = 2;
    766             }
    767 
    768             if (upgradeVersion != DATABASE_VERSION) {
    769                 Log.w(TAG, "Failed to upgrade database!");
    770             }
    771         }
    772     }
    773 
    774     /**
    775      * Cache consistency model:
    776      * - Writes to storage write directly to the cache, but this MUST happen within the atomic
    777      *   section either provided by the database transaction or mWriteLock, such that writes to the
    778      *   cache and writes to the backing storage are guaranteed to occur in the same order
    779      *
    780      * - Reads can populate the cache, but because they are no strong ordering guarantees with
    781      *   respect to writes this precaution is taken:
    782      *   - The cache is assigned a version number that increases every time the cache is modified.
    783      *     Reads from backing storage can only populate the cache if the backing storage
    784      *     has not changed since the load operation has begun.
    785      *     This guarantees that no read operation can shadow a write to the cache that happens
    786      *     after it had begun.
    787      */
    788     private static class Cache {
    789         private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>();
    790         private final CacheKey mCacheKey = new CacheKey();
    791         private int mVersion = 0;
    792 
    793         String peekKeyValue(String key, String defaultValue, int userId) {
    794             Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId);
    795             return cached == DEFAULT ? defaultValue : (String) cached;
    796         }
    797 
    798         boolean hasKeyValue(String key, int userId) {
    799             return contains(CacheKey.TYPE_KEY_VALUE, key, userId);
    800         }
    801 
    802         void putKeyValue(String key, String value, int userId) {
    803             put(CacheKey.TYPE_KEY_VALUE, key, value, userId);
    804         }
    805 
    806         void putKeyValueIfUnchanged(String key, Object value, int userId, int version) {
    807             putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version);
    808         }
    809 
    810         byte[] peekFile(String fileName) {
    811             return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
    812         }
    813 
    814         boolean hasFile(String fileName) {
    815             return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */);
    816         }
    817 
    818         void putFile(String key, byte[] value) {
    819             put(CacheKey.TYPE_FILE, key, value, -1 /* userId */);
    820         }
    821 
    822         void putFileIfUnchanged(String key, byte[] value, int version) {
    823             putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version);
    824         }
    825 
    826         void setFetched(int userId) {
    827             put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId);
    828         }
    829 
    830         boolean isFetched(int userId) {
    831             return contains(CacheKey.TYPE_FETCHED, "", userId);
    832         }
    833 
    834 
    835         private synchronized void put(int type, String key, Object value, int userId) {
    836             // Create a new CachKey here because it may be saved in the map if the key is absent.
    837             mCache.put(new CacheKey().set(type, key, userId), value);
    838             mVersion++;
    839         }
    840 
    841         private synchronized void putIfUnchanged(int type, String key, Object value, int userId,
    842                 int version) {
    843             if (!contains(type, key, userId) && mVersion == version) {
    844                 put(type, key, value, userId);
    845             }
    846         }
    847 
    848         private synchronized boolean contains(int type, String key, int userId) {
    849             return mCache.containsKey(mCacheKey.set(type, key, userId));
    850         }
    851 
    852         private synchronized Object peek(int type, String key, int userId) {
    853             return mCache.get(mCacheKey.set(type, key, userId));
    854         }
    855 
    856         private synchronized int getVersion() {
    857             return mVersion;
    858         }
    859 
    860         synchronized void removeUser(int userId) {
    861             for (int i = mCache.size() - 1; i >= 0; i--) {
    862                 if (mCache.keyAt(i).userId == userId) {
    863                     mCache.removeAt(i);
    864                 }
    865             }
    866 
    867             // Make sure in-flight loads can't write to cache.
    868             mVersion++;
    869         }
    870 
    871         synchronized void purgePath(String path) {
    872             for (int i = mCache.size() - 1; i >= 0; i--) {
    873                 CacheKey entry = mCache.keyAt(i);
    874                 if (entry.type == CacheKey.TYPE_FILE && entry.key.startsWith(path)) {
    875                     mCache.removeAt(i);
    876                 }
    877             }
    878             mVersion++;
    879         }
    880 
    881         synchronized void clear() {
    882             mCache.clear();
    883             mVersion++;
    884         }
    885 
    886         private static final class CacheKey {
    887             static final int TYPE_KEY_VALUE = 0;
    888             static final int TYPE_FILE = 1;
    889             static final int TYPE_FETCHED = 2;
    890 
    891             String key;
    892             int userId;
    893             int type;
    894 
    895             public CacheKey set(int type, String key, int userId) {
    896                 this.type = type;
    897                 this.key = key;
    898                 this.userId = userId;
    899                 return this;
    900             }
    901 
    902             @Override
    903             public boolean equals(Object obj) {
    904                 if (!(obj instanceof CacheKey))
    905                     return false;
    906                 CacheKey o = (CacheKey) obj;
    907                 return userId == o.userId && type == o.type && key.equals(o.key);
    908             }
    909 
    910             @Override
    911             public int hashCode() {
    912                 return key.hashCode() ^ userId ^ type;
    913             }
    914         }
    915     }
    916 
    917 }
    918