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