19066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/* 29066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Copyright (C) 2007 The Android Open Source Project 39066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 49066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 59066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you may not use this file except in compliance with the License. 69066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * You may obtain a copy of the License at 79066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 89066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 99066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * See the License for the specific language governing permissions and 149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * limitations under the License. 159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpackage android.content; 189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.database.ContentObserver; 209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.database.Cursor; 219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport android.os.Handler; 229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.HashMap; 249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Map; 259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectimport java.util.Observable; 269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project/** 289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Caches the contents of a cursor into a Map of String->ContentValues and optionally 299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * keeps the cache fresh by registering for updates on the content backing the cursor. The column of 309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * the database that is to be used as the key of the map is user-configurable, and the 319066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * ContentValues contains all columns other than the one that is designated the key. 329066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * <p> 339066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * The cursor data is accessed by row key and column name via getValue(). 349066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 359066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Projectpublic class ContentQueryMap extends Observable { 36866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana private volatile Cursor mCursor; 379066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private String[] mColumnNames; 389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private int mKeyColumn; 399066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 409066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private Handler mHandlerForUpdateNotifications = null; 419066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private boolean mKeepUpdated = false; 429066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 439066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private Map<String, ContentValues> mValues = null; 449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private ContentObserver mContentObserver; 469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** Set when a cursor change notification is received and is cleared on a call to requery(). */ 489066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project private boolean mDirty = false; 499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Creates a ContentQueryMap that caches the content backing the cursor 529066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * 539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param cursor the cursor whose contents should be cached 549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param columnNameOfKey the column that is to be used as the key of the values map 559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param keepUpdated true if the cursor's ContentProvider should be monitored for changes and 569066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * the map updated when changes do occur 579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param handlerForUpdateNotifications the Handler that should be used to receive 589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * notifications of changes (if requested). Normally you pass null here, but if 599066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * you know that the thread that is creating this isn't a thread that can receive 609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * messages then you can create your own handler and use that here. 619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public ContentQueryMap(Cursor cursor, String columnNameOfKey, boolean keepUpdated, 639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project Handler handlerForUpdateNotifications) { 649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mCursor = cursor; 659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mColumnNames = mCursor.getColumnNames(); 669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mKeyColumn = mCursor.getColumnIndexOrThrow(columnNameOfKey); 679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mHandlerForUpdateNotifications = handlerForUpdateNotifications; 689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project setKeepUpdated(keepUpdated); 699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // If we aren't keeping the cache updated with the current state of the cursor's 719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // ContentProvider then read it once into the cache. Otherwise the cache will be filled 729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // automatically. 739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (!keepUpdated) { 74866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana readCursorIntoCache(cursor); 759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Change whether or not the ContentQueryMap will register with the cursor's ContentProvider 809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * for change notifications. If you use a ContentQueryMap in an activity you should call this 819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * with false in onPause(), which means you need to call it with true in onResume() 829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * if want it to be kept updated. 839066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param keepUpdated if true the ContentQueryMap should be registered with the cursor's 849066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * ContentProvider, false otherwise 859066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 869066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public void setKeepUpdated(boolean keepUpdated) { 879066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (keepUpdated == mKeepUpdated) return; 889066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mKeepUpdated = keepUpdated; 899066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 909066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (!mKeepUpdated) { 919066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mCursor.unregisterContentObserver(mContentObserver); 929066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mContentObserver = null; 939066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 949066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (mHandlerForUpdateNotifications == null) { 959066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mHandlerForUpdateNotifications = new Handler(); 969066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 979066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (mContentObserver == null) { 989066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mContentObserver = new ContentObserver(mHandlerForUpdateNotifications) { 999066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project @Override 1009066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public void onChange(boolean selfChange) { 1019066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // If anyone is listening, we need to do this now to broadcast 1029066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // to the observers. Otherwise, we'll just set mDirty and 1039066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // let it query lazily when they ask for the values. 1049066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (countObservers() != 0) { 1059066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project requery(); 1069066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } else { 1079066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mDirty = true; 1089066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1099066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1109066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project }; 1119066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1129066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mCursor.registerContentObserver(mContentObserver); 1139066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // mark dirty, since it is possible the cursor's backing data had changed before we 1149066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // registered for changes 1159066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mDirty = true; 1169066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1179066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1189066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1199066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** 1209066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * Access the ContentValues for the row specified by rowName 1219066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @param rowName which row to read 1229066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project * @return the ContentValues for the row, or null if the row wasn't present in the cursor 1239066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project */ 1249066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public synchronized ContentValues getValues(String rowName) { 1259066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (mDirty) requery(); 1269066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return mValues.get(rowName); 1279066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1289066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1299066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project /** Requeries the cursor and reads the contents into the cache */ 1309066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public void requery() { 131866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana final Cursor cursor = mCursor; 132866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana if (cursor == null) { 133866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana // If mCursor is null then it means there was a requery() in flight 134866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana // while another thread called close(), which nulls out mCursor. 135866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana // If this happens ignore the requery() since we are closed anyways. 136866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana return; 137866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana } 1389066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mDirty = false; 139866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana if (!cursor.requery()) { 140866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana // again, don't do anything if the cursor is already closed 141866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana return; 142a7dd5eab33d1a87135ae5bd40a02f2e3c564f5deVasu Nori } 143866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana readCursorIntoCache(cursor); 1449066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project setChanged(); 1459066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project notifyObservers(); 1469066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1479066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 148866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana private synchronized void readCursorIntoCache(Cursor cursor) { 1499066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project // Make a new map so old values returned by getRows() are undisturbed. 1509066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project int capacity = mValues != null ? mValues.size() : 0; 1519066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mValues = new HashMap<String, ContentValues>(capacity); 152866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana while (cursor.moveToNext()) { 1539066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project ContentValues values = new ContentValues(); 1549066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project for (int i = 0; i < mColumnNames.length; i++) { 1559066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (i != mKeyColumn) { 156866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana values.put(mColumnNames[i], cursor.getString(i)); 1579066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1589066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 159866647f9b4d78ea5791d7480a459d93629afbf97Fred Quintana mValues.put(cursor.getString(mKeyColumn), values); 1609066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1619066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1629066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1639066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public synchronized Map<String, ContentValues> getRows() { 1649066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (mDirty) requery(); 1659066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project return mValues; 1669066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1679066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1689066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project public synchronized void close() { 1699066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (mContentObserver != null) { 1709066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mCursor.unregisterContentObserver(mContentObserver); 1719066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mContentObserver = null; 1729066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1739066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mCursor.close(); 1749066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project mCursor = null; 1759066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1769066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project 1779066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project @Override 1789066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project protected void finalize() throws Throwable { 1799066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project if (mCursor != null) close(); 1809066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project super.finalize(); 1819066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project } 1829066cfe9886ac131c34d59ed0e2d287b0e3c0087The Android Open Source Project} 183