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