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