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