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