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