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