1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.content; 18 19import android.database.ContentObserver; 20import android.database.Cursor; 21import android.os.Handler; 22 23import java.util.HashMap; 24import java.util.Map; 25import java.util.Observable; 26 27/** 28 * Caches the contents of a cursor into a Map of String->ContentValues and optionally 29 * keeps the cache fresh by registering for updates on the content backing the cursor. The column of 30 * the database that is to be used as the key of the map is user-configurable, and the 31 * ContentValues contains all columns other than the one that is designated the key. 32 * <p> 33 * The cursor data is accessed by row key and column name via getValue(). 34 */ 35public class ContentQueryMap extends Observable { 36 private Cursor mCursor; 37 private String[] mColumnNames; 38 private int mKeyColumn; 39 40 private Handler mHandlerForUpdateNotifications = null; 41 private boolean mKeepUpdated = false; 42 43 private Map<String, ContentValues> mValues = null; 44 45 private ContentObserver mContentObserver; 46 47 /** Set when a cursor change notification is received and is cleared on a call to requery(). */ 48 private boolean mDirty = false; 49 50 /** 51 * Creates a ContentQueryMap that caches the content backing the cursor 52 * 53 * @param cursor the cursor whose contents should be cached 54 * @param columnNameOfKey the column that is to be used as the key of the values map 55 * @param keepUpdated true if the cursor's ContentProvider should be monitored for changes and 56 * the map updated when changes do occur 57 * @param handlerForUpdateNotifications the Handler that should be used to receive 58 * notifications of changes (if requested). Normally you pass null here, but if 59 * you know that the thread that is creating this isn't a thread that can receive 60 * messages then you can create your own handler and use that here. 61 */ 62 public ContentQueryMap(Cursor cursor, String columnNameOfKey, boolean keepUpdated, 63 Handler handlerForUpdateNotifications) { 64 mCursor = cursor; 65 mColumnNames = mCursor.getColumnNames(); 66 mKeyColumn = mCursor.getColumnIndexOrThrow(columnNameOfKey); 67 mHandlerForUpdateNotifications = handlerForUpdateNotifications; 68 setKeepUpdated(keepUpdated); 69 70 // If we aren't keeping the cache updated with the current state of the cursor's 71 // ContentProvider then read it once into the cache. Otherwise the cache will be filled 72 // automatically. 73 if (!keepUpdated) { 74 readCursorIntoCache(); 75 } 76 } 77 78 /** 79 * Change whether or not the ContentQueryMap will register with the cursor's ContentProvider 80 * for change notifications. If you use a ContentQueryMap in an activity you should call this 81 * with false in onPause(), which means you need to call it with true in onResume() 82 * if want it to be kept updated. 83 * @param keepUpdated if true the ContentQueryMap should be registered with the cursor's 84 * ContentProvider, false otherwise 85 */ 86 public void setKeepUpdated(boolean keepUpdated) { 87 if (keepUpdated == mKeepUpdated) return; 88 mKeepUpdated = keepUpdated; 89 90 if (!mKeepUpdated) { 91 mCursor.unregisterContentObserver(mContentObserver); 92 mContentObserver = null; 93 } else { 94 if (mHandlerForUpdateNotifications == null) { 95 mHandlerForUpdateNotifications = new Handler(); 96 } 97 if (mContentObserver == null) { 98 mContentObserver = new ContentObserver(mHandlerForUpdateNotifications) { 99 @Override 100 public void onChange(boolean selfChange) { 101 // If anyone is listening, we need to do this now to broadcast 102 // to the observers. Otherwise, we'll just set mDirty and 103 // let it query lazily when they ask for the values. 104 if (countObservers() != 0) { 105 requery(); 106 } else { 107 mDirty = true; 108 } 109 } 110 }; 111 } 112 mCursor.registerContentObserver(mContentObserver); 113 // mark dirty, since it is possible the cursor's backing data had changed before we 114 // registered for changes 115 mDirty = true; 116 } 117 } 118 119 /** 120 * Access the ContentValues for the row specified by rowName 121 * @param rowName which row to read 122 * @return the ContentValues for the row, or null if the row wasn't present in the cursor 123 */ 124 public synchronized ContentValues getValues(String rowName) { 125 if (mDirty) requery(); 126 return mValues.get(rowName); 127 } 128 129 /** Requeries the cursor and reads the contents into the cache */ 130 public void requery() { 131 mDirty = false; 132 mCursor.requery(); 133 readCursorIntoCache(); 134 setChanged(); 135 notifyObservers(); 136 } 137 138 private synchronized void readCursorIntoCache() { 139 // Make a new map so old values returned by getRows() are undisturbed. 140 int capacity = mValues != null ? mValues.size() : 0; 141 mValues = new HashMap<String, ContentValues>(capacity); 142 while (mCursor.moveToNext()) { 143 ContentValues values = new ContentValues(); 144 for (int i = 0; i < mColumnNames.length; i++) { 145 if (i != mKeyColumn) { 146 values.put(mColumnNames[i], mCursor.getString(i)); 147 } 148 } 149 mValues.put(mCursor.getString(mKeyColumn), values); 150 } 151 } 152 153 public synchronized Map<String, ContentValues> getRows() { 154 if (mDirty) requery(); 155 return mValues; 156 } 157 158 public synchronized void close() { 159 if (mContentObserver != null) { 160 mCursor.unregisterContentObserver(mContentObserver); 161 mContentObserver = null; 162 } 163 mCursor.close(); 164 mCursor = null; 165 } 166 167 @Override 168 protected void finalize() throws Throwable { 169 if (mCursor != null) close(); 170 super.finalize(); 171 } 172} 173