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