1fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank/* 2fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Copyright (C) 2010 The Android Open Source Project 3fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 4fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Licensed under the Apache License, Version 2.0 (the "License"); 5fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * you may not use this file except in compliance with the License. 6fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * You may obtain a copy of the License at 7fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 8fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * http://www.apache.org/licenses/LICENSE-2.0 9fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 10fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Unless required by applicable law or agreed to in writing, software 11fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * distributed under the License is distributed on an "AS IS" BASIS, 12fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * See the License for the specific language governing permissions and 14fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * limitations under the License. 15fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 16fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 17fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blankpackage com.android.email.provider; 182199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler 19fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blankimport android.content.ContentValues; 20d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blankimport android.database.CrossProcessCursor; 21fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blankimport android.database.Cursor; 22d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blankimport android.database.CursorWindow; 23fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blankimport android.database.CursorWrapper; 24fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blankimport android.database.MatrixCursor; 25fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blankimport android.net.Uri; 2692ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilsonimport android.util.LruCache; 276e418aa41a17136be0dddb816d843428a0a1e722Marc Blank 2851c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdonimport com.android.email.DebugUtils; 29560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils; 307fdde9bb4a24e931618a7a64227e2194c89034daScott Kennedyimport com.android.mail.utils.MatrixCursorWithCachedColumns; 316e418aa41a17136be0dddb816d843428a0a1e722Marc Blankimport com.google.common.annotations.VisibleForTesting; 326e418aa41a17136be0dddb816d843428a0a1e722Marc Blank 33fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blankimport java.util.ArrayList; 34fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blankimport java.util.Arrays; 35fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blankimport java.util.HashMap; 36fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blankimport java.util.Map; 372199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadlerimport java.util.Set; 38fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 39fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank/** 40fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * An LRU cache for EmailContent (Account, HostAuth, Mailbox, and Message, thus far). The intended 41fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * user of this cache is EmailProvider itself; caching is entirely transparent to users of the 42fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * provider. 43fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 44fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Usage examples; id is a String representation of a row id (_id), as it might be retrieved from 45fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * a uri via getPathSegment 46fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 47fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * To create a cache: 48fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * ContentCache cache = new ContentCache(name, projection, max); 49fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 50fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * To (try to) get a cursor from a cache: 51fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Cursor cursor = cache.getCursor(id, projection); 52fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 53fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * To read from a table and cache the resulting cursor: 54fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 1. Get a CacheToken: CacheToken token = cache.getToken(id); 55fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 2. Get a cursor from the database: Cursor cursor = db.query(....); 56fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 3. Put the cursor in the cache: cache.putCursor(cursor, id, token); 57fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Only cursors with the projection given in the definition of the cache can be cached 58fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 59fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * To delete one or more rows or update multiple rows from a table that uses cached data: 60fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 1. Lock the row in the cache: cache.lock(id); 61fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 2. Delete/update the row(s): db.delete(...); 62fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 3. Invalidate any other caches that might be affected by the delete/update: 63fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * The entire cache: affectedCache.invalidate()* 64fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * A specific row in a cache: affectedCache.invalidate(rowId) 65fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 4. Unlock the row in the cache: cache.unlock(id); 66fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 67fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * To update a single row from a table that uses cached data: 68fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 1. Lock the row in the cache: cache.lock(id); 69fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 2. Update the row: db.update(...); 70fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 3. Unlock the row in the cache, passing in the new values: cache.unlock(id, values); 71497039234182170ada90e63e96650b5675429ff5Marc Blank * 72497039234182170ada90e63e96650b5675429ff5Marc Blank * Synchronization note: All of the public methods in ContentCache are synchronized (i.e. on the 73497039234182170ada90e63e96650b5675429ff5Marc Blank * cache itself) except for methods that are solely used for debugging and do not modify the cache. 74497039234182170ada90e63e96650b5675429ff5Marc Blank * All references to ContentCache that are external to the ContentCache class MUST synchronize on 75497039234182170ada90e63e96650b5675429ff5Marc Blank * the ContentCache instance (e.g. CachedCursor.close()) 76fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 773aee641aab491a3da53364aafb9074ae69dd2212Jesse Wilsonpublic final class ContentCache { 78788408dee4ef861a5b3196992984d7aad4889992Marc Blank private static final boolean DEBUG_CACHE = false; // DO NOT CHECK IN TRUE 79788408dee4ef861a5b3196992984d7aad4889992Marc Blank private static final boolean DEBUG_TOKENS = false; // DO NOT CHECK IN TRUE 80349055aad47184b72cd86de1f37ac1b7557d2e70Marc Blank private static final boolean DEBUG_NOT_CACHEABLE = false; // DO NOT CHECK IN TRUE 81f40294e1aac1f7cd92c02d8a39edc40676fe3c55Marc Blank private static final boolean DEBUG_STATISTICS = false; // DO NOT CHECK THIS IN TRUE 82349055aad47184b72cd86de1f37ac1b7557d2e70Marc Blank 83349055aad47184b72cd86de1f37ac1b7557d2e70Marc Blank // If false, reads will not use the cache; this is intended for debugging only 84349055aad47184b72cd86de1f37ac1b7557d2e70Marc Blank private static final boolean READ_CACHE_ENABLED = true; // DO NOT CHECK IN FALSE 85fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 86fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Count of non-cacheable queries (debug only) 87fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private static int sNotCacheable = 0; 88fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // A map of queries that aren't cacheable (debug only) 89fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private static final CounterMap<String> sNotCacheableMap = new CounterMap<String>(); 90fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 9192ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson private final LruCache<String, Cursor> mLruCache; 923aee641aab491a3da53364aafb9074ae69dd2212Jesse Wilson 93fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // All defined caches 94fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private static final ArrayList<ContentCache> sContentCaches = new ArrayList<ContentCache>(); 95fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // A set of all unclosed, cached cursors; this will typically be a very small set, as cursors 96fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // tend to be closed quickly after use. The value, for each cursor, is its reference count 976e418aa41a17136be0dddb816d843428a0a1e722Marc Blank /*package*/ static final CounterMap<Cursor> sActiveCursors = new CounterMap<Cursor>(24); 98fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 99fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // A set of locked content id's 100fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private final CounterMap<String> mLockMap = new CounterMap<String>(4); 101fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // A set of active tokens 102fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /*package*/ TokenList mTokenList; 103fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 104fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // The name of the cache (used for logging) 105fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private final String mName; 106fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // The base projection (only queries in which all columns exist in this projection will be 107fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // able to avoid a cache miss) 108fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private final String[] mBaseProjection; 109fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // The tag used for logging 110fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private final String mLogTag; 111fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Cache statistics 112fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private final Statistics mStats; 11378849fd388041d8727325aa654de31dcb8088786Todd Kennedy /** If {@code true}, lock the cache for all writes */ 11478849fd388041d8727325aa654de31dcb8088786Todd Kennedy private static boolean sLockCache; 115fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 116fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 1172199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler * A synchronized reference counter for arbitrary objects 118fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 1192199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler /*package*/ static class CounterMap<T> { 1202199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler private HashMap<T, Integer> mMap; 121fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 122fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /*package*/ CounterMap(int maxSize) { 1232199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler mMap = new HashMap<T, Integer>(maxSize); 124fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 125fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 126fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /*package*/ CounterMap() { 1272199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler mMap = new HashMap<T, Integer>(); 128fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 129fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 130c159d32be05ee744f3726579d9cd8eed39545137Marc Blank /*package*/ synchronized int subtract(T object) { 1312199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler Integer refCount = mMap.get(object); 132c159d32be05ee744f3726579d9cd8eed39545137Marc Blank int newCount; 1332199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler if (refCount == null || refCount.intValue() == 0) { 1342199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler throw new IllegalStateException(); 1352199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler } 1362199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler if (refCount > 1) { 137c159d32be05ee744f3726579d9cd8eed39545137Marc Blank newCount = refCount - 1; 138c159d32be05ee744f3726579d9cd8eed39545137Marc Blank mMap.put(object, newCount); 1392199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler } else { 140c159d32be05ee744f3726579d9cd8eed39545137Marc Blank newCount = 0; 1412199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler mMap.remove(object); 142fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 143c159d32be05ee744f3726579d9cd8eed39545137Marc Blank return newCount; 144fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 145fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 1462199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler /*package*/ synchronized void add(T object) { 1472199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler Integer refCount = mMap.get(object); 1482199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler if (refCount == null) { 1492199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler mMap.put(object, 1); 1502199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler } else { 1512199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler mMap.put(object, refCount + 1); 152fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 153fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 154fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 1552199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler /*package*/ synchronized boolean contains(T object) { 1562199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler return mMap.containsKey(object); 157fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 158fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 1592199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler /*package*/ synchronized int getCount(T object) { 1602199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler Integer refCount = mMap.get(object); 1612199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler return (refCount == null) ? 0 : refCount.intValue(); 1622199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler } 1632199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler 1642199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler synchronized int size() { 1652199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler return mMap.size(); 1662199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler } 1672199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler 1682199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler /** 1692199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler * For Debugging Only - not efficient 1702199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler */ 171f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank synchronized Set<Map.Entry<T, Integer>> entrySet() { 1722199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler return mMap.entrySet(); 173fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 174fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 175fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 176fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 177fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * A list of tokens that are in use at any moment; there can be more than one token for an id 178fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 179fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /*package*/ static class TokenList extends ArrayList<CacheToken> { 180fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private static final long serialVersionUID = 1L; 181fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private final String mLogTag; 182fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 183fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /*package*/ TokenList(String name) { 184fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mLogTag = "TokenList-" + name; 185fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 186fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 187fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /*package*/ int invalidateTokens(String id) { 18851c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG && DEBUG_TOKENS) { 189560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(mLogTag, "============ Invalidate tokens for: " + id); 190fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 191fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank ArrayList<CacheToken> removeList = new ArrayList<CacheToken>(); 192fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank int count = 0; 193fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank for (CacheToken token: this) { 194fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (token.getId().equals(id)) { 195fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank token.invalidate(); 196fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank removeList.add(token); 197fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank count++; 198fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 199fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 200fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank for (CacheToken token: removeList) { 201fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank remove(token); 202fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 203fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return count; 204fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 205fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 206fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /*package*/ void invalidate() { 20751c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG && DEBUG_TOKENS) { 208560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(mLogTag, "============ List invalidated"); 209fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 210fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank for (CacheToken token: this) { 211fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank token.invalidate(); 212fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 213fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank clear(); 214fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 215fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 216fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /*package*/ boolean remove(CacheToken token) { 217fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank boolean result = super.remove(token); 21851c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG && DEBUG_TOKENS) { 219fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (result) { 220560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(mLogTag, "============ Removing token for: " + token.mId); 221fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } else { 222560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(mLogTag, "============ No token found for: " + token.mId); 223fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 224fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 225fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return result; 226fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 227fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 228fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public CacheToken add(String id) { 229fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank CacheToken token = new CacheToken(id); 230fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank super.add(token); 23151c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG && DEBUG_TOKENS) { 232560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(mLogTag, "============ Taking token for: " + token.mId); 233fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 234fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return token; 235fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 236fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 237fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 238fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 239fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * A CacheToken is an opaque object that must be passed into putCursor in order to attempt to 240fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * write into the cache. The token becomes invalidated by any intervening write to the cached 241fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * record. 242fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 243fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public static final class CacheToken { 244fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private final String mId; 245349055aad47184b72cd86de1f37ac1b7557d2e70Marc Blank private boolean mIsValid = READ_CACHE_ENABLED; 246fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 247fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /*package*/ CacheToken(String id) { 248fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mId = id; 249fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 250fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 251fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /*package*/ String getId() { 252fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return mId; 253fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 254fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 255fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /*package*/ boolean isValid() { 256fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return mIsValid; 257fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 258fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 259fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /*package*/ void invalidate() { 260fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mIsValid = false; 261fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 262fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 263fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 264fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public boolean equals(Object token) { 265fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return ((token instanceof CacheToken) && ((CacheToken)token).mId.equals(mId)); 266fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 267fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 268fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 269fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public int hashCode() { 270fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return mId.hashCode(); 271fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 272fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 273fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 274fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 275fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * The cached cursor is simply a CursorWrapper whose underlying cursor contains zero or one 276fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * rows. We handle simple movement (moveToFirst(), moveToNext(), etc.), and override close() 27768dc380d62b29b4b6733bc1b20e44b8931c1d341Marc Blank * to keep the underlying cursor alive (unless it's no longer cached due to an invalidation). 27868dc380d62b29b4b6733bc1b20e44b8931c1d341Marc Blank * Multiple CachedCursor's can use the same underlying cursor, so we override the various 27968dc380d62b29b4b6733bc1b20e44b8931c1d341Marc Blank * moveX methods such that each CachedCursor can have its own position information 280fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 281d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank public static final class CachedCursor extends CursorWrapper implements CrossProcessCursor { 282fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // The cursor we're wrapping 283fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private final Cursor mCursor; 284fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // The cache which generated this cursor 285fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private final ContentCache mCache; 28692ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson private final String mId; 287fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // The current position of the cursor (can only be 0 or 1) 288fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private int mPosition = -1; 289fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // The number of rows in this cursor (-1 = not determined) 290fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private int mCount = -1; 291fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private boolean isClosed = false; 292fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 29392ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson public CachedCursor(Cursor cursor, ContentCache cache, String id) { 294fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank super(cursor); 295fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mCursor = cursor; 296fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mCache = cache; 29792ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson mId = id; 298fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Add this to our set of active cursors 299fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank sActiveCursors.add(cursor); 300fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 301fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 302fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 303c159d32be05ee744f3726579d9cd8eed39545137Marc Blank * Close this cursor; if the cursor's cache no longer contains the underlying cursor, and 304c159d32be05ee744f3726579d9cd8eed39545137Marc Blank * there are no other users of that cursor, we'll close it here. In any event, 305c159d32be05ee744f3726579d9cd8eed39545137Marc Blank * we'll remove the cursor from our set of active cursors. 306fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 307fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 308fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public void close() { 309497039234182170ada90e63e96650b5675429ff5Marc Blank synchronized(mCache) { 310788408dee4ef861a5b3196992984d7aad4889992Marc Blank int count = sActiveCursors.subtract(mCursor); 31192ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson if ((count == 0) && mCache.mLruCache.get(mId) != (mCursor)) { 312497039234182170ada90e63e96650b5675429ff5Marc Blank super.close(); 313497039234182170ada90e63e96650b5675429ff5Marc Blank } 314fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 315fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank isClosed = true; 316fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 317fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 318fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 319fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public boolean isClosed() { 320fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return isClosed; 321fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 322fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 323fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 324fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public int getCount() { 325fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (mCount < 0) { 326fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mCount = super.getCount(); 327fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 328fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return mCount; 329fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 330fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 331fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 33268dc380d62b29b4b6733bc1b20e44b8931c1d341Marc Blank * We'll be happy to move to position 0 or -1 333fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 334fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 335fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public boolean moveToPosition(int pos) { 33668dc380d62b29b4b6733bc1b20e44b8931c1d341Marc Blank if (pos >= getCount() || pos < -1) { 337fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return false; 338fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 339fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mPosition = pos; 340fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return true; 341fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 342fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 343fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 344fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public boolean moveToFirst() { 345fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return moveToPosition(0); 346fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 347fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 348fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 349fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public boolean moveToNext() { 350fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return moveToPosition(mPosition + 1); 351fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 352fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 353fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 354fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public boolean moveToPrevious() { 35568dc380d62b29b4b6733bc1b20e44b8931c1d341Marc Blank return moveToPosition(mPosition - 1); 356fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 357fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 358fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 359fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public int getPosition() { 360fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return mPosition; 361fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 362fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 363fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 364fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public final boolean move(int offset) { 365fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return moveToPosition(mPosition + offset); 366fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 367fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 368fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 369fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public final boolean moveToLast() { 370fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return moveToPosition(getCount() - 1); 371fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 372fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 373fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 374fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public final boolean isLast() { 375fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return mPosition == (getCount() - 1); 376fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 377fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 378fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 379fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public final boolean isBeforeFirst() { 380fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return mPosition == -1; 381fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 382fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 383fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 384fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public final boolean isAfterLast() { 385fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return mPosition == 1; 386fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 387d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank 388d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank @Override 389d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank public CursorWindow getWindow() { 390d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank return ((CrossProcessCursor)mCursor).getWindow(); 391d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank } 392d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank 393d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank @Override 394d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank public void fillWindow(int pos, CursorWindow window) { 395d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank ((CrossProcessCursor)mCursor).fillWindow(pos, window); 396d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank } 397d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank 398d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank @Override 399d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank public boolean onMove(int oldPosition, int newPosition) { 400a91f33b44b967496d16ef5ed443219df3d97d8c4Marc Blank return true; 401d12f78d6bac81590f97fc190723865ffe65e5d69Marc Blank } 402fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 403fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 404fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 405fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Public constructor 406fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param name the name of the cache (used for logging) 407fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param baseProjection the projection used for cached cursors; queries whose columns are not 408fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * included in baseProjection will always generate a cache miss 409fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param maxSize the maximum number of content cursors to cache 410fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 411fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public ContentCache(String name, String[] baseProjection, int maxSize) { 412fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mName = name; 41392ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson mLruCache = new LruCache<String, Cursor>(maxSize) { 41492ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson @Override 4155d29dac8065e75b040aeb29401630fd65fedb9fcJesse Wilson protected void entryRemoved( 4165d29dac8065e75b040aeb29401630fd65fedb9fcJesse Wilson boolean evicted, String key, Cursor oldValue, Cursor newValue) { 41792ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson // Close this cursor if it's no longer being used 4185d29dac8065e75b040aeb29401630fd65fedb9fcJesse Wilson if (evicted && !sActiveCursors.contains(oldValue)) { 4195d29dac8065e75b040aeb29401630fd65fedb9fcJesse Wilson oldValue.close(); 42092ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson } 42192ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson } 42292ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson }; 423fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mBaseProjection = baseProjection; 424fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mLogTag = "ContentCache-" + name; 425fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank sContentCaches.add(this); 426fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mTokenList = new TokenList(mName); 427fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStats = new Statistics(this); 428fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 429fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 430fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 431fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Return the base projection for cached rows 432fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Get the projection used for cached rows (typically, the largest possible projection) 433fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @return 434fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 435fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public String[] getProjection() { 436fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return mBaseProjection; 437fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 438fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 439fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 440fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 441fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Get a CacheToken for a row as specified by its id (_id column) 442fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param id the id of the record 443fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @return a CacheToken needed in order to write data for the record back to the cache 444fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 445fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public synchronized CacheToken getCacheToken(String id) { 446fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // If another thread is already writing the data, return an invalid token 447fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank CacheToken token = mTokenList.add(id); 448fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (mLockMap.contains(id)) { 449fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank token.invalidate(); 450fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 451fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return token; 452fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 453fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 4543aee641aab491a3da53364aafb9074ae69dd2212Jesse Wilson public int size() { 45592ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson return mLruCache.size(); 4563aee641aab491a3da53364aafb9074ae69dd2212Jesse Wilson } 4573aee641aab491a3da53364aafb9074ae69dd2212Jesse Wilson 4586e418aa41a17136be0dddb816d843428a0a1e722Marc Blank @VisibleForTesting 4596e418aa41a17136be0dddb816d843428a0a1e722Marc Blank Cursor get(String id) { 46092ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson return mLruCache.get(id); 461fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 462fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 4636e418aa41a17136be0dddb816d843428a0a1e722Marc Blank protected Map<String, Cursor> getSnapshot() { 4646e418aa41a17136be0dddb816d843428a0a1e722Marc Blank return mLruCache.snapshot(); 4656e418aa41a17136be0dddb816d843428a0a1e722Marc Blank } 466fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 467fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Try to cache a cursor for the given id and projection; returns a valid cursor, either a 468fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * cached cursor (if caching was successful) or the original cursor 469fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 470fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param c the cursor to be cached 471fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param id the record id (_id) of the content 472fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param projection the projection represented by the cursor 473fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @return whether or not the cursor was cached 474fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 4755835dcecedb4f6a7bd3473cce172de0fb699cf98Marc Blank public Cursor putCursor(Cursor c, String id, String[] projection, CacheToken token) { 4765835dcecedb4f6a7bd3473cce172de0fb699cf98Marc Blank // Make sure the underlying cursor is at the first row, and do this without synchronizing, 4775835dcecedb4f6a7bd3473cce172de0fb699cf98Marc Blank // to prevent deadlock with a writing thread (which might, for example, be calling into 4785835dcecedb4f6a7bd3473cce172de0fb699cf98Marc Blank // CachedCursor.invalidate) 4795835dcecedb4f6a7bd3473cce172de0fb699cf98Marc Blank c.moveToPosition(0); 4805835dcecedb4f6a7bd3473cce172de0fb699cf98Marc Blank return putCursorImpl(c, id, projection, token); 4815835dcecedb4f6a7bd3473cce172de0fb699cf98Marc Blank } 4825835dcecedb4f6a7bd3473cce172de0fb699cf98Marc Blank public synchronized Cursor putCursorImpl(Cursor c, String id, String[] projection, 483fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank CacheToken token) { 484fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank try { 485fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (!token.isValid()) { 48651c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG && DEBUG_CACHE) { 487560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(mLogTag, "============ Stale token for " + id); 488fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 489fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStats.mStaleCount++; 490fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return c; 491fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 4926e418aa41a17136be0dddb816d843428a0a1e722Marc Blank if (c != null && Arrays.equals(projection, mBaseProjection) && !sLockCache) { 49351c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG && DEBUG_CACHE) { 494560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(mLogTag, "============ Caching cursor for: " + id); 495fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 496fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // If we've already cached this cursor, invalidate the older one 497fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank Cursor existingCursor = get(id); 498fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (existingCursor != null) { 499fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank unlockImpl(id, null, false); 500fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 50192ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson mLruCache.put(id, c); 502fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return new CachedCursor(c, this, id); 503fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 504fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return c; 505fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } finally { 506fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mTokenList.remove(token); 507fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 508fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 509fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 510fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 511fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Find and, if found, return a cursor, based on cached values, for the supplied id 512fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param id the _id column of the desired row 513fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param projection the requested projection for a query 514fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @return a cursor based on cached values, or null if the row is not cached 515fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 516fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public synchronized Cursor getCachedCursor(String id, String[] projection) { 51751c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG && DEBUG_STATISTICS) { 518fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Every 200 calls to getCursor, report cache statistics 519fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank dumpOnCount(200); 520fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 521fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (projection == mBaseProjection) { 522fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return getCachedCursorImpl(id); 523fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } else { 524fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return getMatrixCursor(id, projection); 525fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 526fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 527fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 528fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private CachedCursor getCachedCursorImpl(String id) { 529fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank Cursor c = get(id); 530fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (c != null) { 531fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStats.mHitCount++; 532fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return new CachedCursor(c, this, id); 533fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 534fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStats.mMissCount++; 535fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return null; 536fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 537fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 538fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private MatrixCursor getMatrixCursor(String id, String[] projection) { 539fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return getMatrixCursor(id, projection, null); 540fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 541fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 542fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private MatrixCursor getMatrixCursor(String id, String[] projection, 543fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank ContentValues values) { 544fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank Cursor c = get(id); 545fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (c != null) { 546fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Make a new MatrixCursor with the requested columns 5477fdde9bb4a24e931618a7a64227e2194c89034daScott Kennedy MatrixCursor mc = new MatrixCursorWithCachedColumns(projection, 1); 548d9b251d23b30e25cf388fbbc1a9bdbb3f7caeebdMarc Blank if (c.getCount() == 0) { 549d9b251d23b30e25cf388fbbc1a9bdbb3f7caeebdMarc Blank return mc; 550d9b251d23b30e25cf388fbbc1a9bdbb3f7caeebdMarc Blank } 551fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank Object[] row = new Object[projection.length]; 552fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (values != null) { 553fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Make a copy; we don't want to change the original 554fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank values = new ContentValues(values); 555fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 556fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank int i = 0; 557fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank for (String column: projection) { 558fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank int columnIndex = c.getColumnIndex(column); 559fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (columnIndex < 0) { 560fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStats.mProjectionMissCount++; 561fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return null; 562fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } else { 563fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank String value; 564fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (values != null && values.containsKey(column)) { 565fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank Object val = values.get(column); 566fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (val instanceof Boolean) { 567fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank value = (val == Boolean.TRUE) ? "1" : "0"; 568fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } else { 569fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank value = values.getAsString(column); 570fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 571fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank values.remove(column); 572fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } else { 573fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank value = c.getString(columnIndex); 574fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 575fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank row[i++] = value; 576fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 577fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 578fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (values != null && values.size() != 0) { 579fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return null; 580fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 581fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mc.addRow(row); 582fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStats.mHitCount++; 583fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return mc; 584fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 585fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStats.mMissCount++; 586fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return null; 587fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 588fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 589fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 590fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Lock a given row, such that no new valid CacheTokens can be created for the passed-in id. 591fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param id the id of the row to lock 592fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 593fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public synchronized void lock(String id) { 594fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Prevent new valid tokens from being created 595fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mLockMap.add(id); 596fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Invalidate current tokens 597fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank int count = mTokenList.invalidateTokens(id); 59851c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG && DEBUG_TOKENS) { 599560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(mTokenList.mLogTag, "============ Lock invalidated " + count + 600fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank " tokens for: " + id); 601fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 602fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 603fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 604fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 605fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Unlock a given row, allowing new valid CacheTokens to be created for the passed-in id. 606fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param id the id of the item whose cursor is cached 607fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 608fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public synchronized void unlock(String id) { 609fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank unlockImpl(id, null, true); 610fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 611fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 612fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 613fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * If the row with id is currently cached, replaces the cached values with the supplied 614fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * ContentValues. Then, unlock the row, so that new valid CacheTokens can be created. 615fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 616fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param id the id of the item whose cursor is cached 617fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param values updated values for this row 618fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 619fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public synchronized void unlock(String id, ContentValues values) { 620fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank unlockImpl(id, values, true); 621fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 622fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 623fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 624fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * If values are passed in, replaces any cached cursor with one containing new values, and 625fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * then closes the previously cached one (if any, and if not in use) 626fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * If values are not passed in, removes the row from cache 627fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * If the row was locked, unlock it 628fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param id the id of the row 629fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param values new ContentValues for the row (or null if row should simply be removed) 630fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param wasLocked whether or not the row was locked; if so, the lock will be removed 631fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 632497039234182170ada90e63e96650b5675429ff5Marc Blank private void unlockImpl(String id, ContentValues values, boolean wasLocked) { 633fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank Cursor c = get(id); 634fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (c != null) { 63551c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG && DEBUG_CACHE) { 636560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(mLogTag, "=========== Unlocking cache for: " + id); 637fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 63878849fd388041d8727325aa654de31dcb8088786Todd Kennedy if (values != null && !sLockCache) { 639fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank MatrixCursor cursor = getMatrixCursor(id, mBaseProjection, values); 640fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (cursor != null) { 64151c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG && DEBUG_CACHE) { 642560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(mLogTag, "=========== Recaching with new values: " + id); 643fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 644fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank cursor.moveToFirst(); 64592ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson mLruCache.put(id, cursor); 646fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } else { 64792ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson mLruCache.remove(id); 648fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 649fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } else { 65092ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson mLruCache.remove(id); 651fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 652fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // If there are no cursors using the old cached cursor, close it 653fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (!sActiveCursors.contains(c)) { 654fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank c.close(); 655fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 656fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 657fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (wasLocked) { 6582199c7ddf5d497e816bef1a1b7473098369a1bdfAndy Stadler mLockMap.subtract(id); 659fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 660fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 661fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 662fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 663fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Invalidate the entire cache, without logging 664fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 665fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public synchronized void invalidate() { 666fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank invalidate(null, null, null); 667fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 668fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 669fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /** 670fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * Invalidate the entire cache; the arguments are used for logging only, and indicate the 671fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * write operation that caused the invalidation 672fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * 673fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param operation a string describing the operation causing the invalidate (or null) 674fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param uri the uri causing the invalidate (or null) 675fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank * @param selection the selection used with the uri (or null) 676fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank */ 677fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public synchronized void invalidate(String operation, Uri uri, String selection) { 678fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (DEBUG_CACHE && (operation != null)) { 679560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(mLogTag, "============ INVALIDATED BY " + operation + ": " + uri + 680fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank ", SELECTION: " + selection); 681fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 682fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStats.mInvalidateCount++; 683fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Close all cached cursors that are no longer in use 68492ab6db38dcf15f6935463845fc4fa749a292b3aJesse Wilson mLruCache.evictAll(); 685fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Invalidate all current tokens 686fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mTokenList.invalidate(); 687fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 688fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 689fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Debugging code below 690fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 691fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private void dumpOnCount(int num) { 692fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStats.mOpCount++; 693fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if ((mStats.mOpCount % num) == 0) { 694fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank dumpStats(); 695fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 696fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 697fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 698fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank /*package*/ void recordQueryTime(Cursor c, long nanoTime) { 699fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (c instanceof CachedCursor) { 700fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStats.hitTimes += nanoTime; 701fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStats.hits++; 702fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } else { 703fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (c.getCount() == 1) { 704fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStats.missTimes += nanoTime; 705fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStats.miss++; 706fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 707fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 708fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 709fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 710fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public static synchronized void notCacheable(Uri uri, String selection) { 711fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (DEBUG_NOT_CACHEABLE) { 712fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank sNotCacheable++; 713fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank String str = uri.toString() + "$" + selection; 714fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank sNotCacheableMap.add(str); 715fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 716fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 717fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 718349055aad47184b72cd86de1f37ac1b7557d2e70Marc Blank // For use with unit tests 7196e418aa41a17136be0dddb816d843428a0a1e722Marc Blank public static void invalidateAllCaches() { 720349055aad47184b72cd86de1f37ac1b7557d2e70Marc Blank for (ContentCache cache: sContentCaches) { 721349055aad47184b72cd86de1f37ac1b7557d2e70Marc Blank cache.invalidate(); 722349055aad47184b72cd86de1f37ac1b7557d2e70Marc Blank } 723349055aad47184b72cd86de1f37ac1b7557d2e70Marc Blank } 724349055aad47184b72cd86de1f37ac1b7557d2e70Marc Blank 72578849fd388041d8727325aa654de31dcb8088786Todd Kennedy /** Sets the cache lock. If the lock is {@code true}, also invalidates all cached items. */ 72678849fd388041d8727325aa654de31dcb8088786Todd Kennedy public static void setLockCacheForTest(boolean lock) { 72778849fd388041d8727325aa654de31dcb8088786Todd Kennedy sLockCache = lock; 72878849fd388041d8727325aa654de31dcb8088786Todd Kennedy if (sLockCache) { 7296e418aa41a17136be0dddb816d843428a0a1e722Marc Blank invalidateAllCaches(); 73078849fd388041d8727325aa654de31dcb8088786Todd Kennedy } 73178849fd388041d8727325aa654de31dcb8088786Todd Kennedy } 73278849fd388041d8727325aa654de31dcb8088786Todd Kennedy 733fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank static class Statistics { 734fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private final ContentCache mCache; 735fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private final String mName; 736fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 737fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Cache statistics 738fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // The item is in the cache AND is used to create a cursor 739fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private int mHitCount = 0; 740fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Basic cache miss (the item is not cached) 741fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private int mMissCount = 0; 742fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Incremented when a cachePut is invalid due to an intervening write 743fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private int mStaleCount = 0; 744fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // A projection miss occurs when the item is cached, but not all requested columns are 745fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // available in the base projection 746fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private int mProjectionMissCount = 0; 747fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Incremented whenever the entire cache is invalidated 748fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private int mInvalidateCount = 0; 749fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Count of operations put/get 750fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private int mOpCount = 0; 751fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // The following are for timing statistics 752fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private long hits = 0; 753fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private long hitTimes = 0; 754fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private long miss = 0; 755fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private long missTimes = 0; 756fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 757fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank // Used in toString() and addCacheStatistics() 758fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private int mCursorCount = 0; 759fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private int mTokenCount = 0; 760fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 761fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank Statistics(ContentCache cache) { 762fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mCache = cache; 763fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mName = mCache.mName; 764fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 765fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 766fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank Statistics(String name) { 767fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mCache = null; 768fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mName = name; 769fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 770fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 771fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank private void addCacheStatistics(ContentCache cache) { 772fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (cache != null) { 773fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mHitCount += cache.mStats.mHitCount; 774fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mMissCount += cache.mStats.mMissCount; 775fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mProjectionMissCount += cache.mStats.mProjectionMissCount; 776fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mStaleCount += cache.mStats.mStaleCount; 777fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank hitTimes += cache.mStats.hitTimes; 778fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank missTimes += cache.mStats.missTimes; 779fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank hits += cache.mStats.hits; 780fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank miss += cache.mStats.miss; 781fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mCursorCount += cache.size(); 782fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank mTokenCount += cache.mTokenList.size(); 783fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 784fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 785fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 7861b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy private static void append(StringBuilder sb, String name, Object value) { 787fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank sb.append(", "); 788fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank sb.append(name); 789fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank sb.append(": "); 790fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank sb.append(value); 791fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 792fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 793fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank @Override 794fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public String toString() { 795fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (mHitCount + mMissCount == 0) return "No cache"; 796fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank int totalTries = mMissCount + mProjectionMissCount + mHitCount; 797fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank StringBuilder sb = new StringBuilder(); 798fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank sb.append("Cache " + mName); 799fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank append(sb, "Cursors", mCache == null ? mCursorCount : mCache.size()); 800fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank append(sb, "Hits", mHitCount); 801fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank append(sb, "Misses", mMissCount + mProjectionMissCount); 802fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank append(sb, "Inval", mInvalidateCount); 803fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank append(sb, "Tokens", mCache == null ? mTokenCount : mCache.mTokenList.size()); 804fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank append(sb, "Hit%", mHitCount * 100 / totalTries); 805fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank append(sb, "\nHit time", hitTimes / 1000000.0 / hits); 806fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank append(sb, "Miss time", missTimes / 1000000.0 / miss); 807fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank return sb.toString(); 808fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 809fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 810fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 811fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank public static void dumpStats() { 812fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank Statistics totals = new Statistics("Totals"); 813fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank 814fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank for (ContentCache cache: sContentCaches) { 815fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank if (cache != null) { 816560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(cache.mName, cache.mStats.toString()); 817fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank totals.addCacheStatistics(cache); 818fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 819fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 820560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(totals.mName, totals.toString()); 821fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank } 822fab77f147f85766d2f75d8aece0aaa4ffb3838e8Marc Blank} 823