ContentQueryMap.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
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