/*
 * 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.

The cursor data is accessed by row key and column name via getValue().

/** * 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().
/** 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
Params:
  • cursor – the cursor whose contents should be cached
  • columnNameOfKey – the column that is to be used as the key of the values map
  • keepUpdated – true if the cursor's ContentProvider should be monitored for changes and the map updated when changes do occur
  • 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.
/** * 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.
Params:
  • keepUpdated – if true the ContentQueryMap should be registered with the cursor's ContentProvider, false otherwise
/** * 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
Params:
  • rowName – which row to read
Returns:the ContentValues for the row, or null if the row wasn't present in the cursor
/** * 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
/** 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(); } }