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; 18 19 import static android.content.Context.USER_SERVICE; 20 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.content.pm.UserInfo; 24 import android.database.Cursor; 25 import android.database.sqlite.SQLiteDatabase; 26 import android.database.sqlite.SQLiteOpenHelper; 27 import android.os.Environment; 28 import android.os.UserManager; 29 import android.os.storage.StorageManager; 30 import android.util.ArrayMap; 31 import android.util.Log; 32 import android.util.Slog; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.util.ArrayUtils; 36 import com.android.internal.widget.LockPatternUtils; 37 38 import java.io.File; 39 import java.io.IOException; 40 import java.io.RandomAccessFile; 41 42 /** 43 * Storage for the lock settings service. 44 */ 45 class LockSettingsStorage { 46 47 private static final String TAG = "LockSettingsStorage"; 48 private static final String TABLE = "locksettings"; 49 private static final boolean DEBUG = false; 50 51 private static final String COLUMN_KEY = "name"; 52 private static final String COLUMN_USERID = "user"; 53 private static final String COLUMN_VALUE = "value"; 54 55 private static final String[] COLUMNS_FOR_QUERY = { 56 COLUMN_VALUE 57 }; 58 private static final String[] COLUMNS_FOR_PREFETCH = { 59 COLUMN_KEY, COLUMN_VALUE 60 }; 61 62 private static final String SYSTEM_DIRECTORY = "/system/"; 63 private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key"; 64 private static final String BASE_ZERO_LOCK_PATTERN_FILE = "gatekeeper.gesture.key"; 65 private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key"; 66 private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key"; 67 private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key"; 68 private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key"; 69 70 private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/"; 71 72 private static final Object DEFAULT = new Object(); 73 74 private final DatabaseHelper mOpenHelper; 75 private final Context mContext; 76 private final Cache mCache = new Cache(); 77 private final Object mFileWriteLock = new Object(); 78 79 @VisibleForTesting 80 public static class CredentialHash { 81 static final int VERSION_LEGACY = 0; 82 static final int VERSION_GATEKEEPER = 1; 83 CredentialHash(byte[] hash, int type, int version)84 private CredentialHash(byte[] hash, int type, int version) { 85 if (type != LockPatternUtils.CREDENTIAL_TYPE_NONE) { 86 if (hash == null) { 87 throw new RuntimeException("Empty hash for CredentialHash"); 88 } 89 } else /* type == LockPatternUtils.CREDENTIAL_TYPE_NONE */ { 90 if (hash != null) { 91 throw new RuntimeException("None type CredentialHash should not have hash"); 92 } 93 } 94 this.hash = hash; 95 this.type = type; 96 this.version = version; 97 this.isBaseZeroPattern = false; 98 } 99 CredentialHash(byte[] hash, boolean isBaseZeroPattern)100 private CredentialHash(byte[] hash, boolean isBaseZeroPattern) { 101 this.hash = hash; 102 this.type = LockPatternUtils.CREDENTIAL_TYPE_PATTERN; 103 this.version = VERSION_GATEKEEPER; 104 this.isBaseZeroPattern = isBaseZeroPattern; 105 } 106 create(byte[] hash, int type)107 static CredentialHash create(byte[] hash, int type) { 108 if (type == LockPatternUtils.CREDENTIAL_TYPE_NONE) { 109 throw new RuntimeException("Bad type for CredentialHash"); 110 } 111 return new CredentialHash(hash, type, VERSION_GATEKEEPER); 112 } 113 createEmptyHash()114 static CredentialHash createEmptyHash() { 115 return new CredentialHash(null, LockPatternUtils.CREDENTIAL_TYPE_NONE, 116 VERSION_GATEKEEPER); 117 } 118 119 byte[] hash; 120 int type; 121 int version; 122 boolean isBaseZeroPattern; 123 } 124 LockSettingsStorage(Context context)125 public LockSettingsStorage(Context context) { 126 mContext = context; 127 mOpenHelper = new DatabaseHelper(context); 128 } 129 setDatabaseOnCreateCallback(Callback callback)130 public void setDatabaseOnCreateCallback(Callback callback) { 131 mOpenHelper.setCallback(callback); 132 } 133 writeKeyValue(String key, String value, int userId)134 public void writeKeyValue(String key, String value, int userId) { 135 writeKeyValue(mOpenHelper.getWritableDatabase(), key, value, userId); 136 } 137 writeKeyValue(SQLiteDatabase db, String key, String value, int userId)138 public void writeKeyValue(SQLiteDatabase db, String key, String value, int userId) { 139 ContentValues cv = new ContentValues(); 140 cv.put(COLUMN_KEY, key); 141 cv.put(COLUMN_USERID, userId); 142 cv.put(COLUMN_VALUE, value); 143 144 db.beginTransaction(); 145 try { 146 db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", 147 new String[] {key, Integer.toString(userId)}); 148 db.insert(TABLE, null, cv); 149 db.setTransactionSuccessful(); 150 mCache.putKeyValue(key, value, userId); 151 } finally { 152 db.endTransaction(); 153 } 154 155 } 156 readKeyValue(String key, String defaultValue, int userId)157 public String readKeyValue(String key, String defaultValue, int userId) { 158 int version; 159 synchronized (mCache) { 160 if (mCache.hasKeyValue(key, userId)) { 161 return mCache.peekKeyValue(key, defaultValue, userId); 162 } 163 version = mCache.getVersion(); 164 } 165 166 Cursor cursor; 167 Object result = DEFAULT; 168 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 169 if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY, 170 COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?", 171 new String[] { Integer.toString(userId), key }, 172 null, null, null)) != null) { 173 if (cursor.moveToFirst()) { 174 result = cursor.getString(0); 175 } 176 cursor.close(); 177 } 178 mCache.putKeyValueIfUnchanged(key, result, userId, version); 179 return result == DEFAULT ? defaultValue : (String) result; 180 } 181 prefetchUser(int userId)182 public void prefetchUser(int userId) { 183 int version; 184 synchronized (mCache) { 185 if (mCache.isFetched(userId)) { 186 return; 187 } 188 mCache.setFetched(userId); 189 version = mCache.getVersion(); 190 } 191 192 Cursor cursor; 193 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 194 if ((cursor = db.query(TABLE, COLUMNS_FOR_PREFETCH, 195 COLUMN_USERID + "=?", 196 new String[] { Integer.toString(userId) }, 197 null, null, null)) != null) { 198 while (cursor.moveToNext()) { 199 String key = cursor.getString(0); 200 String value = cursor.getString(1); 201 mCache.putKeyValueIfUnchanged(key, value, userId, version); 202 } 203 cursor.close(); 204 } 205 206 // Populate cache by reading the password and pattern files. 207 readCredentialHash(userId); 208 } 209 readPasswordHashIfExists(int userId)210 private CredentialHash readPasswordHashIfExists(int userId) { 211 byte[] stored = readFile(getLockPasswordFilename(userId)); 212 if (!ArrayUtils.isEmpty(stored)) { 213 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 214 CredentialHash.VERSION_GATEKEEPER); 215 } 216 217 stored = readFile(getLegacyLockPasswordFilename(userId)); 218 if (!ArrayUtils.isEmpty(stored)) { 219 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 220 CredentialHash.VERSION_LEGACY); 221 } 222 return null; 223 } 224 readPatternHashIfExists(int userId)225 private CredentialHash readPatternHashIfExists(int userId) { 226 byte[] stored = readFile(getLockPatternFilename(userId)); 227 if (!ArrayUtils.isEmpty(stored)) { 228 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 229 CredentialHash.VERSION_GATEKEEPER); 230 } 231 232 stored = readFile(getBaseZeroLockPatternFilename(userId)); 233 if (!ArrayUtils.isEmpty(stored)) { 234 return new CredentialHash(stored, true); 235 } 236 237 stored = readFile(getLegacyLockPatternFilename(userId)); 238 if (!ArrayUtils.isEmpty(stored)) { 239 return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 240 CredentialHash.VERSION_LEGACY); 241 } 242 return null; 243 } 244 readCredentialHash(int userId)245 public CredentialHash readCredentialHash(int userId) { 246 CredentialHash passwordHash = readPasswordHashIfExists(userId); 247 CredentialHash patternHash = readPatternHashIfExists(userId); 248 if (passwordHash != null && patternHash != null) { 249 if (passwordHash.version == CredentialHash.VERSION_GATEKEEPER) { 250 return passwordHash; 251 } else { 252 return patternHash; 253 } 254 } else if (passwordHash != null) { 255 return passwordHash; 256 } else if (patternHash != null) { 257 return patternHash; 258 } else { 259 return CredentialHash.createEmptyHash(); 260 } 261 } 262 removeChildProfileLock(int userId)263 public void removeChildProfileLock(int userId) { 264 if (DEBUG) 265 Slog.e(TAG, "Remove child profile lock for user: " + userId); 266 try { 267 deleteFile(getChildProfileLockFile(userId)); 268 } catch (Exception e) { 269 e.printStackTrace(); 270 } 271 } 272 writeChildProfileLock(int userId, byte[] lock)273 public void writeChildProfileLock(int userId, byte[] lock) { 274 writeFile(getChildProfileLockFile(userId), lock); 275 } 276 readChildProfileLock(int userId)277 public byte[] readChildProfileLock(int userId) { 278 return readFile(getChildProfileLockFile(userId)); 279 } 280 hasChildProfileLock(int userId)281 public boolean hasChildProfileLock(int userId) { 282 return hasFile(getChildProfileLockFile(userId)); 283 } 284 hasPassword(int userId)285 public boolean hasPassword(int userId) { 286 return hasFile(getLockPasswordFilename(userId)) || 287 hasFile(getLegacyLockPasswordFilename(userId)); 288 } 289 hasPattern(int userId)290 public boolean hasPattern(int userId) { 291 return hasFile(getLockPatternFilename(userId)) || 292 hasFile(getBaseZeroLockPatternFilename(userId)) || 293 hasFile(getLegacyLockPatternFilename(userId)); 294 } 295 hasCredential(int userId)296 public boolean hasCredential(int userId) { 297 return hasPassword(userId) || hasPattern(userId); 298 } 299 hasFile(String name)300 private boolean hasFile(String name) { 301 byte[] contents = readFile(name); 302 return contents != null && contents.length > 0; 303 } 304 readFile(String name)305 private byte[] readFile(String name) { 306 int version; 307 synchronized (mCache) { 308 if (mCache.hasFile(name)) { 309 return mCache.peekFile(name); 310 } 311 version = mCache.getVersion(); 312 } 313 314 RandomAccessFile raf = null; 315 byte[] stored = null; 316 try { 317 raf = new RandomAccessFile(name, "r"); 318 stored = new byte[(int) raf.length()]; 319 raf.readFully(stored, 0, stored.length); 320 raf.close(); 321 } catch (IOException e) { 322 Slog.e(TAG, "Cannot read file " + e); 323 } finally { 324 if (raf != null) { 325 try { 326 raf.close(); 327 } catch (IOException e) { 328 Slog.e(TAG, "Error closing file " + e); 329 } 330 } 331 } 332 mCache.putFileIfUnchanged(name, stored, version); 333 return stored; 334 } 335 writeFile(String name, byte[] hash)336 private void writeFile(String name, byte[] hash) { 337 synchronized (mFileWriteLock) { 338 RandomAccessFile raf = null; 339 try { 340 // Write the hash to file, requiring each write to be synchronized to the 341 // underlying storage device immediately to avoid data loss in case of power loss. 342 // This also ensures future secdiscard operation on the file succeeds since the 343 // file would have been allocated on flash. 344 raf = new RandomAccessFile(name, "rws"); 345 // Truncate the file if pattern is null, to clear the lock 346 if (hash == null || hash.length == 0) { 347 raf.setLength(0); 348 } else { 349 raf.write(hash, 0, hash.length); 350 } 351 raf.close(); 352 } catch (IOException e) { 353 Slog.e(TAG, "Error writing to file " + e); 354 } finally { 355 if (raf != null) { 356 try { 357 raf.close(); 358 } catch (IOException e) { 359 Slog.e(TAG, "Error closing file " + e); 360 } 361 } 362 } 363 mCache.putFile(name, hash); 364 } 365 } 366 deleteFile(String name)367 private void deleteFile(String name) { 368 if (DEBUG) Slog.e(TAG, "Delete file " + name); 369 synchronized (mFileWriteLock) { 370 File file = new File(name); 371 if (file.exists()) { 372 file.delete(); 373 mCache.putFile(name, null); 374 } 375 } 376 } 377 writeCredentialHash(CredentialHash hash, int userId)378 public void writeCredentialHash(CredentialHash hash, int userId) { 379 byte[] patternHash = null; 380 byte[] passwordHash = null; 381 382 if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) { 383 passwordHash = hash.hash; 384 } else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) { 385 patternHash = hash.hash; 386 } 387 writeFile(getLockPasswordFilename(userId), passwordHash); 388 writeFile(getLockPatternFilename(userId), patternHash); 389 } 390 391 @VisibleForTesting getLockPatternFilename(int userId)392 String getLockPatternFilename(int userId) { 393 return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE); 394 } 395 396 @VisibleForTesting getLockPasswordFilename(int userId)397 String getLockPasswordFilename(int userId) { 398 return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE); 399 } 400 401 @VisibleForTesting getLegacyLockPatternFilename(int userId)402 String getLegacyLockPatternFilename(int userId) { 403 return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PATTERN_FILE); 404 } 405 406 @VisibleForTesting getLegacyLockPasswordFilename(int userId)407 String getLegacyLockPasswordFilename(int userId) { 408 return getLockCredentialFilePathForUser(userId, LEGACY_LOCK_PASSWORD_FILE); 409 } 410 getBaseZeroLockPatternFilename(int userId)411 private String getBaseZeroLockPatternFilename(int userId) { 412 return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE); 413 } 414 415 @VisibleForTesting getChildProfileLockFile(int userId)416 String getChildProfileLockFile(int userId) { 417 return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE); 418 } 419 getLockCredentialFilePathForUser(int userId, String basename)420 private String getLockCredentialFilePathForUser(int userId, String basename) { 421 String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() + 422 SYSTEM_DIRECTORY; 423 if (userId == 0) { 424 // Leave it in the same place for user 0 425 return dataSystemDirectory + basename; 426 } else { 427 return new File(Environment.getUserSystemDirectory(userId), basename).getAbsolutePath(); 428 } 429 } 430 writeSyntheticPasswordState(int userId, long handle, String name, byte[] data)431 public void writeSyntheticPasswordState(int userId, long handle, String name, byte[] data) { 432 writeFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name), data); 433 } 434 readSyntheticPasswordState(int userId, long handle, String name)435 public byte[] readSyntheticPasswordState(int userId, long handle, String name) { 436 return readFile(getSynthenticPasswordStateFilePathForUser(userId, handle, name)); 437 } 438 deleteSyntheticPasswordState(int userId, long handle, String name)439 public void deleteSyntheticPasswordState(int userId, long handle, String name) { 440 String path = getSynthenticPasswordStateFilePathForUser(userId, handle, name); 441 File file = new File(path); 442 if (file.exists()) { 443 try { 444 mContext.getSystemService(StorageManager.class).secdiscard(file.getAbsolutePath()); 445 } catch (Exception e) { 446 Slog.w(TAG, "Failed to secdiscard " + path, e); 447 } finally { 448 file.delete(); 449 } 450 mCache.putFile(path, null); 451 } 452 } 453 454 @VisibleForTesting getSyntheticPasswordDirectoryForUser(int userId)455 protected File getSyntheticPasswordDirectoryForUser(int userId) { 456 return new File(Environment.getDataSystemDeDirectory(userId) ,SYNTHETIC_PASSWORD_DIRECTORY); 457 } 458 459 @VisibleForTesting getSynthenticPasswordStateFilePathForUser(int userId, long handle, String name)460 protected String getSynthenticPasswordStateFilePathForUser(int userId, long handle, 461 String name) { 462 File baseDir = getSyntheticPasswordDirectoryForUser(userId); 463 String baseName = String.format("%016x.%s", handle, name); 464 if (!baseDir.exists()) { 465 baseDir.mkdir(); 466 } 467 return new File(baseDir, baseName).getAbsolutePath(); 468 } 469 removeUser(int userId)470 public void removeUser(int userId) { 471 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 472 473 final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); 474 final UserInfo parentInfo = um.getProfileParent(userId); 475 476 if (parentInfo == null) { 477 // This user owns its lock settings files - safe to delete them 478 synchronized (mFileWriteLock) { 479 String name = getLockPasswordFilename(userId); 480 File file = new File(name); 481 if (file.exists()) { 482 file.delete(); 483 mCache.putFile(name, null); 484 } 485 name = getLockPatternFilename(userId); 486 file = new File(name); 487 if (file.exists()) { 488 file.delete(); 489 mCache.putFile(name, null); 490 } 491 } 492 } else { 493 // Managed profile 494 removeChildProfileLock(userId); 495 } 496 497 File spStateDir = getSyntheticPasswordDirectoryForUser(userId); 498 try { 499 db.beginTransaction(); 500 db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null); 501 db.setTransactionSuccessful(); 502 mCache.removeUser(userId); 503 // The directory itself will be deleted as part of user deletion operation by the 504 // framework, so only need to purge cache here. 505 //TODO: (b/34600579) invoke secdiscardable 506 mCache.purgePath(spStateDir.getAbsolutePath()); 507 } finally { 508 db.endTransaction(); 509 } 510 } 511 512 @VisibleForTesting closeDatabase()513 void closeDatabase() { 514 mOpenHelper.close(); 515 } 516 517 @VisibleForTesting clearCache()518 void clearCache() { 519 mCache.clear(); 520 } 521 522 public interface Callback { initialize(SQLiteDatabase db)523 void initialize(SQLiteDatabase db); 524 } 525 526 class DatabaseHelper extends SQLiteOpenHelper { 527 private static final String TAG = "LockSettingsDB"; 528 private static final String DATABASE_NAME = "locksettings.db"; 529 530 private static final int DATABASE_VERSION = 2; 531 532 private Callback mCallback; 533 DatabaseHelper(Context context)534 public DatabaseHelper(Context context) { 535 super(context, DATABASE_NAME, null, DATABASE_VERSION); 536 setWriteAheadLoggingEnabled(true); 537 } 538 setCallback(Callback callback)539 public void setCallback(Callback callback) { 540 mCallback = callback; 541 } 542 createTable(SQLiteDatabase db)543 private void createTable(SQLiteDatabase db) { 544 db.execSQL("CREATE TABLE " + TABLE + " (" + 545 "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 546 COLUMN_KEY + " TEXT," + 547 COLUMN_USERID + " INTEGER," + 548 COLUMN_VALUE + " TEXT" + 549 ");"); 550 } 551 552 @Override onCreate(SQLiteDatabase db)553 public void onCreate(SQLiteDatabase db) { 554 createTable(db); 555 if (mCallback != null) { 556 mCallback.initialize(db); 557 } 558 } 559 560 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion)561 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { 562 int upgradeVersion = oldVersion; 563 if (upgradeVersion == 1) { 564 // Previously migrated lock screen widget settings. Now defunct. 565 upgradeVersion = 2; 566 } 567 568 if (upgradeVersion != DATABASE_VERSION) { 569 Log.w(TAG, "Failed to upgrade database!"); 570 } 571 } 572 } 573 574 /** 575 * Cache consistency model: 576 * - Writes to storage write directly to the cache, but this MUST happen within the atomic 577 * section either provided by the database transaction or mWriteLock, such that writes to the 578 * cache and writes to the backing storage are guaranteed to occur in the same order 579 * 580 * - Reads can populate the cache, but because they are no strong ordering guarantees with 581 * respect to writes this precaution is taken: 582 * - The cache is assigned a version number that increases every time the cache is modified. 583 * Reads from backing storage can only populate the cache if the backing storage 584 * has not changed since the load operation has begun. 585 * This guarantees that no read operation can shadow a write to the cache that happens 586 * after it had begun. 587 */ 588 private static class Cache { 589 private final ArrayMap<CacheKey, Object> mCache = new ArrayMap<>(); 590 private final CacheKey mCacheKey = new CacheKey(); 591 private int mVersion = 0; 592 peekKeyValue(String key, String defaultValue, int userId)593 String peekKeyValue(String key, String defaultValue, int userId) { 594 Object cached = peek(CacheKey.TYPE_KEY_VALUE, key, userId); 595 return cached == DEFAULT ? defaultValue : (String) cached; 596 } 597 hasKeyValue(String key, int userId)598 boolean hasKeyValue(String key, int userId) { 599 return contains(CacheKey.TYPE_KEY_VALUE, key, userId); 600 } 601 putKeyValue(String key, String value, int userId)602 void putKeyValue(String key, String value, int userId) { 603 put(CacheKey.TYPE_KEY_VALUE, key, value, userId); 604 } 605 putKeyValueIfUnchanged(String key, Object value, int userId, int version)606 void putKeyValueIfUnchanged(String key, Object value, int userId, int version) { 607 putIfUnchanged(CacheKey.TYPE_KEY_VALUE, key, value, userId, version); 608 } 609 peekFile(String fileName)610 byte[] peekFile(String fileName) { 611 return (byte[]) peek(CacheKey.TYPE_FILE, fileName, -1 /* userId */); 612 } 613 hasFile(String fileName)614 boolean hasFile(String fileName) { 615 return contains(CacheKey.TYPE_FILE, fileName, -1 /* userId */); 616 } 617 putFile(String key, byte[] value)618 void putFile(String key, byte[] value) { 619 put(CacheKey.TYPE_FILE, key, value, -1 /* userId */); 620 } 621 putFileIfUnchanged(String key, byte[] value, int version)622 void putFileIfUnchanged(String key, byte[] value, int version) { 623 putIfUnchanged(CacheKey.TYPE_FILE, key, value, -1 /* userId */, version); 624 } 625 setFetched(int userId)626 void setFetched(int userId) { 627 put(CacheKey.TYPE_FETCHED, "isFetched", "true", userId); 628 } 629 isFetched(int userId)630 boolean isFetched(int userId) { 631 return contains(CacheKey.TYPE_FETCHED, "", userId); 632 } 633 634 put(int type, String key, Object value, int userId)635 private synchronized void put(int type, String key, Object value, int userId) { 636 // Create a new CachKey here because it may be saved in the map if the key is absent. 637 mCache.put(new CacheKey().set(type, key, userId), value); 638 mVersion++; 639 } 640 putIfUnchanged(int type, String key, Object value, int userId, int version)641 private synchronized void putIfUnchanged(int type, String key, Object value, int userId, 642 int version) { 643 if (!contains(type, key, userId) && mVersion == version) { 644 put(type, key, value, userId); 645 } 646 } 647 contains(int type, String key, int userId)648 private synchronized boolean contains(int type, String key, int userId) { 649 return mCache.containsKey(mCacheKey.set(type, key, userId)); 650 } 651 peek(int type, String key, int userId)652 private synchronized Object peek(int type, String key, int userId) { 653 return mCache.get(mCacheKey.set(type, key, userId)); 654 } 655 getVersion()656 private synchronized int getVersion() { 657 return mVersion; 658 } 659 removeUser(int userId)660 synchronized void removeUser(int userId) { 661 for (int i = mCache.size() - 1; i >= 0; i--) { 662 if (mCache.keyAt(i).userId == userId) { 663 mCache.removeAt(i); 664 } 665 } 666 667 // Make sure in-flight loads can't write to cache. 668 mVersion++; 669 } 670 purgePath(String path)671 synchronized void purgePath(String path) { 672 for (int i = mCache.size() - 1; i >= 0; i--) { 673 CacheKey entry = mCache.keyAt(i); 674 if (entry.type == CacheKey.TYPE_FILE && entry.key.startsWith(path)) { 675 mCache.removeAt(i); 676 } 677 } 678 mVersion++; 679 } 680 clear()681 synchronized void clear() { 682 mCache.clear(); 683 mVersion++; 684 } 685 686 private static final class CacheKey { 687 static final int TYPE_KEY_VALUE = 0; 688 static final int TYPE_FILE = 1; 689 static final int TYPE_FETCHED = 2; 690 691 String key; 692 int userId; 693 int type; 694 set(int type, String key, int userId)695 public CacheKey set(int type, String key, int userId) { 696 this.type = type; 697 this.key = key; 698 this.userId = userId; 699 return this; 700 } 701 702 @Override equals(Object obj)703 public boolean equals(Object obj) { 704 if (!(obj instanceof CacheKey)) 705 return false; 706 CacheKey o = (CacheKey) obj; 707 return userId == o.userId && type == o.type && key.equals(o.key); 708 } 709 710 @Override hashCode()711 public int hashCode() { 712 return key.hashCode() ^ userId ^ type; 713 } 714 } 715 } 716 717 } 718