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