| /* |
| * 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(); |
| } |
| } |