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