blob: 8aeaa8f8340a05b9fdb9e28fa1858c76cc88b37a [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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 {
Fred Quintana866647f2010-11-10 12:45:53 -080036 private volatile Cursor mCursor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037 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) {
Fred Quintana866647f2010-11-10 12:45:53 -080074 readCursorIntoCache(cursor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 }
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() {
Fred Quintana866647f2010-11-10 12:45:53 -0800131 final Cursor cursor = mCursor;
132 if (cursor == null) {
133 // If mCursor is null then it means there was a requery() in flight
134 // while another thread called close(), which nulls out mCursor.
135 // If this happens ignore the requery() since we are closed anyways.
136 return;
Vasu Noria7dd5ea2010-08-04 11:57:51 -0700137 }
Fred Quintana866647f2010-11-10 12:45:53 -0800138 mDirty = false;
139 if (!cursor.requery()) {
140 // again, don't do anything if the cursor is already closed
141 return;
142 }
143 readCursorIntoCache(cursor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 setChanged();
145 notifyObservers();
146 }
147
Fred Quintana866647f2010-11-10 12:45:53 -0800148 private synchronized void readCursorIntoCache(Cursor cursor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 // Make a new map so old values returned by getRows() are undisturbed.
150 int capacity = mValues != null ? mValues.size() : 0;
151 mValues = new HashMap<String, ContentValues>(capacity);
Fred Quintana866647f2010-11-10 12:45:53 -0800152 while (cursor.moveToNext()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 ContentValues values = new ContentValues();
154 for (int i = 0; i < mColumnNames.length; i++) {
155 if (i != mKeyColumn) {
Fred Quintana866647f2010-11-10 12:45:53 -0800156 values.put(mColumnNames[i], cursor.getString(i));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157 }
158 }
Fred Quintana866647f2010-11-10 12:45:53 -0800159 mValues.put(cursor.getString(mKeyColumn), values);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 }
161 }
162
163 public synchronized Map<String, ContentValues> getRows() {
164 if (mDirty) requery();
165 return mValues;
166 }
167
168 public synchronized void close() {
169 if (mContentObserver != null) {
170 mCursor.unregisterContentObserver(mContentObserver);
171 mContentObserver = null;
172 }
173 mCursor.close();
174 mCursor = null;
175 }
176
177 @Override
178 protected void finalize() throws Throwable {
179 if (mCursor != null) close();
180 super.finalize();
181 }
182}