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