blob: da90a9f40294454b1aea6dc4e0b21ac8b6165681 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 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.widget;
18
19import android.content.Context;
20import android.database.ContentObserver;
21import android.database.Cursor;
22import android.database.DataSetObserver;
23import android.os.Handler;
24import android.util.Config;
25import android.util.Log;
26import android.view.View;
27import android.view.ViewGroup;
28
29/**
30 * Adapter that exposes data from a {@link android.database.Cursor Cursor} to a
31 * {@link android.widget.ListView ListView} widget. The Cursor must include
32 * a column named "_id" or this class will not work.
33 */
34public abstract class CursorAdapter extends BaseAdapter implements Filterable,
35 CursorFilter.CursorFilterClient {
36 /**
37 * This field should be made private, so it is hidden from the SDK.
38 * {@hide}
39 */
40 protected boolean mDataValid;
41 /**
42 * This field should be made private, so it is hidden from the SDK.
43 * {@hide}
44 */
45 protected boolean mAutoRequery;
46 /**
47 * This field should be made private, so it is hidden from the SDK.
48 * {@hide}
49 */
50 protected Cursor mCursor;
51 /**
52 * This field should be made private, so it is hidden from the SDK.
53 * {@hide}
54 */
55 protected Context mContext;
56 /**
57 * This field should be made private, so it is hidden from the SDK.
58 * {@hide}
59 */
60 protected int mRowIDColumn;
61 /**
62 * This field should be made private, so it is hidden from the SDK.
63 * {@hide}
64 */
65 protected ChangeObserver mChangeObserver;
66 /**
67 * This field should be made private, so it is hidden from the SDK.
68 * {@hide}
69 */
70 protected DataSetObserver mDataSetObserver = new MyDataSetObserver();
71 /**
72 * This field should be made private, so it is hidden from the SDK.
73 * {@hide}
74 */
75 protected CursorFilter mCursorFilter;
76 /**
77 * This field should be made private, so it is hidden from the SDK.
78 * {@hide}
79 */
80 protected FilterQueryProvider mFilterQueryProvider;
81
82 /**
83 * Constructor. The adapter will call requery() on the cursor whenever
84 * it changes so that the most recent data is always displayed.
85 *
86 * @param c The cursor from which to get the data.
87 * @param context The context
88 */
89 public CursorAdapter(Context context, Cursor c) {
90 init(context, c, true);
91 }
92
93 /**
94 * Constructor
95 * @param c The cursor from which to get the data.
96 * @param context The context
97 * @param autoRequery If true the adapter will call requery() on the
98 * cursor whenever it changes so the most recent
99 * data is always displayed.
100 */
101 public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
102 init(context, c, autoRequery);
103 }
104
105 protected void init(Context context, Cursor c, boolean autoRequery) {
106 boolean cursorPresent = c != null;
107 mAutoRequery = autoRequery;
108 mCursor = c;
109 mDataValid = cursorPresent;
110 mContext = context;
111 mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
112 mChangeObserver = new ChangeObserver();
113 if (cursorPresent) {
114 c.registerContentObserver(mChangeObserver);
115 c.registerDataSetObserver(mDataSetObserver);
116 }
117 }
118
119 /**
120 * Returns the cursor.
121 * @return the cursor.
122 */
123 public Cursor getCursor() {
124 return mCursor;
125 }
126
127 /**
128 * @see android.widget.ListAdapter#getCount()
129 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700130 public int getCount() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 if (mDataValid && mCursor != null) {
132 return mCursor.getCount();
133 } else {
134 return 0;
135 }
136 }
137
138 /**
139 * @see android.widget.ListAdapter#getItem(int)
140 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700141 public Object getItem(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 if (mDataValid && mCursor != null) {
143 mCursor.moveToPosition(position);
144 return mCursor;
145 } else {
146 return null;
147 }
148 }
149
150 /**
151 * @see android.widget.ListAdapter#getItemId(int)
152 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700153 public long getItemId(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 if (mDataValid && mCursor != null) {
155 if (mCursor.moveToPosition(position)) {
156 return mCursor.getLong(mRowIDColumn);
157 } else {
158 return 0;
159 }
160 } else {
161 return 0;
162 }
163 }
164
165 @Override
166 public boolean hasStableIds() {
167 return true;
168 }
169
170 /**
171 * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
172 */
173 public View getView(int position, View convertView, ViewGroup parent) {
174 if (!mDataValid) {
175 throw new IllegalStateException("this should only be called when the cursor is valid");
176 }
177 if (!mCursor.moveToPosition(position)) {
178 throw new IllegalStateException("couldn't move cursor to position " + position);
179 }
180 View v;
181 if (convertView == null) {
182 v = newView(mContext, mCursor, parent);
183 } else {
184 v = convertView;
185 }
186 bindView(v, mContext, mCursor);
187 return v;
188 }
189
190 @Override
191 public View getDropDownView(int position, View convertView, ViewGroup parent) {
192 if (mDataValid) {
193 mCursor.moveToPosition(position);
194 View v;
195 if (convertView == null) {
196 v = newDropDownView(mContext, mCursor, parent);
197 } else {
198 v = convertView;
199 }
200 bindView(v, mContext, mCursor);
201 return v;
202 } else {
203 return null;
204 }
205 }
206
207 /**
208 * Makes a new view to hold the data pointed to by cursor.
209 * @param context Interface to application's global information
210 * @param cursor The cursor from which to get the data. The cursor is already
211 * moved to the correct position.
212 * @param parent The parent to which the new view is attached to
213 * @return the newly created view.
214 */
215 public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
216
217 /**
218 * Makes a new drop down view to hold the data pointed to by cursor.
219 * @param context Interface to application's global information
220 * @param cursor The cursor from which to get the data. The cursor is already
221 * moved to the correct position.
222 * @param parent The parent to which the new view is attached to
223 * @return the newly created view.
224 */
225 public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
226 return newView(context, cursor, parent);
227 }
228
229 /**
230 * Bind an existing view to the data pointed to by cursor
231 * @param view Existing view, returned earlier by newView
232 * @param context Interface to application's global information
233 * @param cursor The cursor from which to get the data. The cursor is already
234 * moved to the correct position.
235 */
236 public abstract void bindView(View view, Context context, Cursor cursor);
237
238 /**
239 * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
240 * closed.
241 *
242 * @param cursor the new cursor to be used
243 */
244 public void changeCursor(Cursor cursor) {
245 if (cursor == mCursor) {
246 return;
247 }
248 if (mCursor != null) {
249 mCursor.unregisterContentObserver(mChangeObserver);
250 mCursor.unregisterDataSetObserver(mDataSetObserver);
251 mCursor.close();
252 }
253 mCursor = cursor;
254 if (cursor != null) {
255 cursor.registerContentObserver(mChangeObserver);
256 cursor.registerDataSetObserver(mDataSetObserver);
257 mRowIDColumn = cursor.getColumnIndexOrThrow("_id");
258 mDataValid = true;
259 // notify the observers about the new cursor
260 notifyDataSetChanged();
261 } else {
262 mRowIDColumn = -1;
263 mDataValid = false;
264 // notify the observers about the lack of a data set
265 notifyDataSetInvalidated();
266 }
267 }
268
269 /**
270 * <p>Converts the cursor into a CharSequence. Subclasses should override this
271 * method to convert their results. The default implementation returns an
272 * empty String for null values or the default String representation of
273 * the value.</p>
274 *
275 * @param cursor the cursor to convert to a CharSequence
276 * @return a CharSequence representing the value
277 */
278 public CharSequence convertToString(Cursor cursor) {
279 return cursor == null ? "" : cursor.toString();
280 }
281
282 /**
283 * Runs a query with the specified constraint. This query is requested
284 * by the filter attached to this adapter.
285 *
286 * The query is provided by a
287 * {@link android.widget.FilterQueryProvider}.
288 * If no provider is specified, the current cursor is not filtered and returned.
289 *
290 * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
291 * and the previous cursor is closed.
292 *
293 * This method is always executed on a background thread, not on the
294 * application's main thread (or UI thread.)
295 *
296 * Contract: when constraint is null or empty, the original results,
297 * prior to any filtering, must be returned.
298 *
299 * @param constraint the constraint with which the query must be filtered
300 *
301 * @return a Cursor representing the results of the new query
302 *
303 * @see #getFilter()
304 * @see #getFilterQueryProvider()
305 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
306 */
307 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
308 if (mFilterQueryProvider != null) {
309 return mFilterQueryProvider.runQuery(constraint);
310 }
311
312 return mCursor;
313 }
314
315 public Filter getFilter() {
316 if (mCursorFilter == null) {
317 mCursorFilter = new CursorFilter(this);
318 }
319 return mCursorFilter;
320 }
321
322 /**
323 * Returns the query filter provider used for filtering. When the
324 * provider is null, no filtering occurs.
325 *
326 * @return the current filter query provider or null if it does not exist
327 *
328 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
329 * @see #runQueryOnBackgroundThread(CharSequence)
330 */
331 public FilterQueryProvider getFilterQueryProvider() {
332 return mFilterQueryProvider;
333 }
334
335 /**
336 * Sets the query filter provider used to filter the current Cursor.
337 * The provider's
338 * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
339 * method is invoked when filtering is requested by a client of
340 * this adapter.
341 *
342 * @param filterQueryProvider the filter query provider or null to remove it
343 *
344 * @see #getFilterQueryProvider()
345 * @see #runQueryOnBackgroundThread(CharSequence)
346 */
347 public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
348 mFilterQueryProvider = filterQueryProvider;
349 }
350
351 /**
352 * Called when the {@link ContentObserver} on the cursor receives a change notification.
353 * The default implementation provides the auto-requery logic, but may be overridden by
354 * sub classes.
355 *
356 * @see ContentObserver#onChange(boolean)
357 * @hide pending API Council approval
358 */
359 protected void onContentChanged() {
360 if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
361 if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
362 mDataValid = mCursor.requery();
363 }
364 }
365
366 private class ChangeObserver extends ContentObserver {
367 public ChangeObserver() {
368 super(new Handler());
369 }
370
371 @Override
372 public boolean deliverSelfNotifications() {
373 return true;
374 }
375
376 @Override
377 public void onChange(boolean selfChange) {
378 onContentChanged();
379 }
380 }
381
382 private class MyDataSetObserver extends DataSetObserver {
383 @Override
384 public void onChanged() {
385 mDataValid = true;
386 notifyDataSetChanged();
387 }
388
389 @Override
390 public void onInvalidated() {
391 mDataValid = false;
392 notifyDataSetInvalidated();
393 }
394 }
395
396}