blob: 516162aab9a4316f4bb4033c55b01396c400c346 [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 */
Dianne Hackbornc9189352010-12-15 14:57:25 -080070 protected DataSetObserver mDataSetObserver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071 /**
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 /**
Jeff Hamilton451513b2010-03-31 23:54:11 -050083 * If set the adapter will call requery() on the cursor whenever a content change
Dianne Hackbornc9189352010-12-15 14:57:25 -080084 * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
85 *
86 * @deprecated This option is discouraged, as it results in Cursor queries
87 * being performed on the application's UI thread and thus can cause poor
88 * responsiveness or even Application Not Responding errors. As an alternative,
89 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
Jeff Hamilton451513b2010-03-31 23:54:11 -050090 */
Dianne Hackbornc9189352010-12-15 14:57:25 -080091 @Deprecated
Jeff Hamilton451513b2010-03-31 23:54:11 -050092 public static final int FLAG_AUTO_REQUERY = 0x01;
93
94 /**
95 * If set the adapter will register a content observer on the cursor and will call
Dianne Hackbornc9189352010-12-15 14:57:25 -080096 * {@link #onContentChanged()} when a notification comes in. Be careful when
97 * using this flag: you will need to unset the current Cursor from the adapter
98 * to avoid leaks due to its registered observers. This flag is not needed
99 * when using a CursorAdapter with a
100 * {@link android.content.CursorLoader}.
Jeff Hamilton451513b2010-03-31 23:54:11 -0500101 */
102 public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
103
104 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800105 * Constructor that always enables auto-requery.
106 *
107 * @deprecated This option is discouraged, as it results in Cursor queries
108 * being performed on the application's UI thread and thus can cause poor
109 * responsiveness or even Application Not Responding errors. As an alternative,
110 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111 *
112 * @param c The cursor from which to get the data.
113 * @param context The context
114 */
Dianne Hackbornc9189352010-12-15 14:57:25 -0800115 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 public CursorAdapter(Context context, Cursor c) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500117 init(context, c, FLAG_AUTO_REQUERY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118 }
119
120 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800121 * Constructor that allows control over auto-requery. It is recommended
122 * you not use this, but instead {@link #CursorAdapter(Context, Cursor, int)}.
123 * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
124 * will always be set.
125 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126 * @param c The cursor from which to get the data.
127 * @param context The context
128 * @param autoRequery If true the adapter will call requery() on the
129 * cursor whenever it changes so the most recent
Dianne Hackbornc9189352010-12-15 14:57:25 -0800130 * data is always displayed. Using true here is discouraged.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 */
132 public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
Jeff Hamilton69e87f92010-04-22 11:26:00 -0500133 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
Jeff Hamilton451513b2010-03-31 23:54:11 -0500134 }
135
136 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800137 * Recommended constructor.
138 *
Jeff Hamilton451513b2010-03-31 23:54:11 -0500139 * @param c The cursor from which to get the data.
140 * @param context The context
Dianne Hackbornc9189352010-12-15 14:57:25 -0800141 * @param flags Flags used to determine the behavior of the adapter; may
142 * be any combination of {@link #FLAG_AUTO_REQUERY} and
143 * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
Jeff Hamilton451513b2010-03-31 23:54:11 -0500144 */
145 public CursorAdapter(Context context, Cursor c, int flags) {
146 init(context, c, flags);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147 }
148
Dianne Hackbornc9189352010-12-15 14:57:25 -0800149 /**
150 * @deprecated Don't use this, use the normal constructor. This will
151 * be removed in the future.
152 */
153 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 protected void init(Context context, Cursor c, boolean autoRequery) {
Jeff Hamilton69e87f92010-04-22 11:26:00 -0500155 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
Jeff Hamilton451513b2010-03-31 23:54:11 -0500156 }
157
Dianne Hackbornc9189352010-12-15 14:57:25 -0800158 void init(Context context, Cursor c, int flags) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500159 if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
160 flags |= FLAG_REGISTER_CONTENT_OBSERVER;
161 mAutoRequery = true;
162 } else {
163 mAutoRequery = false;
164 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 boolean cursorPresent = c != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800166 mCursor = c;
167 mDataValid = cursorPresent;
168 mContext = context;
169 mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
Jeff Hamilton451513b2010-03-31 23:54:11 -0500170 if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
171 mChangeObserver = new ChangeObserver();
Dianne Hackbornc9189352010-12-15 14:57:25 -0800172 mDataSetObserver = new MyDataSetObserver();
Jeff Hamilton451513b2010-03-31 23:54:11 -0500173 } else {
174 mChangeObserver = null;
Dianne Hackbornc9189352010-12-15 14:57:25 -0800175 mDataSetObserver = null;
Jeff Hamilton451513b2010-03-31 23:54:11 -0500176 }
177
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 if (cursorPresent) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500179 if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
Dianne Hackbornc9189352010-12-15 14:57:25 -0800180 if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 }
182 }
183
184 /**
185 * Returns the cursor.
186 * @return the cursor.
187 */
188 public Cursor getCursor() {
189 return mCursor;
190 }
191
192 /**
193 * @see android.widget.ListAdapter#getCount()
194 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700195 public int getCount() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800196 if (mDataValid && mCursor != null) {
197 return mCursor.getCount();
198 } else {
199 return 0;
200 }
201 }
202
203 /**
204 * @see android.widget.ListAdapter#getItem(int)
205 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700206 public Object getItem(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800207 if (mDataValid && mCursor != null) {
208 mCursor.moveToPosition(position);
209 return mCursor;
210 } else {
211 return null;
212 }
213 }
214
215 /**
216 * @see android.widget.ListAdapter#getItemId(int)
217 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700218 public long getItemId(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800219 if (mDataValid && mCursor != null) {
220 if (mCursor.moveToPosition(position)) {
221 return mCursor.getLong(mRowIDColumn);
222 } else {
223 return 0;
224 }
225 } else {
226 return 0;
227 }
228 }
229
230 @Override
231 public boolean hasStableIds() {
232 return true;
233 }
234
235 /**
236 * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
237 */
238 public View getView(int position, View convertView, ViewGroup parent) {
239 if (!mDataValid) {
240 throw new IllegalStateException("this should only be called when the cursor is valid");
241 }
242 if (!mCursor.moveToPosition(position)) {
243 throw new IllegalStateException("couldn't move cursor to position " + position);
244 }
245 View v;
246 if (convertView == null) {
247 v = newView(mContext, mCursor, parent);
248 } else {
249 v = convertView;
250 }
251 bindView(v, mContext, mCursor);
252 return v;
253 }
254
255 @Override
256 public View getDropDownView(int position, View convertView, ViewGroup parent) {
257 if (mDataValid) {
258 mCursor.moveToPosition(position);
259 View v;
260 if (convertView == null) {
261 v = newDropDownView(mContext, mCursor, parent);
262 } else {
263 v = convertView;
264 }
265 bindView(v, mContext, mCursor);
266 return v;
267 } else {
268 return null;
269 }
270 }
271
272 /**
273 * Makes a new view to hold the data pointed to by cursor.
274 * @param context Interface to application's global information
275 * @param cursor The cursor from which to get the data. The cursor is already
276 * moved to the correct position.
277 * @param parent The parent to which the new view is attached to
278 * @return the newly created view.
279 */
280 public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
281
282 /**
283 * Makes a new drop down view to hold the data pointed to by cursor.
284 * @param context Interface to application's global information
285 * @param cursor The cursor from which to get the data. The cursor is already
286 * moved to the correct position.
287 * @param parent The parent to which the new view is attached to
288 * @return the newly created view.
289 */
290 public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
291 return newView(context, cursor, parent);
292 }
293
294 /**
295 * Bind an existing view to the data pointed to by cursor
296 * @param view Existing view, returned earlier by newView
297 * @param context Interface to application's global information
298 * @param cursor The cursor from which to get the data. The cursor is already
299 * moved to the correct position.
300 */
301 public abstract void bindView(View view, Context context, Cursor cursor);
302
303 /**
304 * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
305 * closed.
306 *
Dianne Hackbornc9189352010-12-15 14:57:25 -0800307 * @param cursor The new cursor to be used
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800308 */
309 public void changeCursor(Cursor cursor) {
Dianne Hackbornc9189352010-12-15 14:57:25 -0800310 Cursor old = swapCursor(cursor);
311 if (old != null) {
312 old.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800313 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800314 }
315
316 /**
317 * Swap in a new Cursor, returning the old Cursor. Unlike
318 * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
319 * closed.
320 *
321 * @param newCursor The new cursor to be used.
322 * @return Returns the previously set Cursor, or null if there wasa not one.
323 * If the given new Cursor is the same instance is the previously set
324 * Cursor, null is also returned.
325 */
326 public Cursor swapCursor(Cursor newCursor) {
327 if (newCursor == mCursor) {
328 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800330 Cursor oldCursor = mCursor;
331 if (oldCursor != null) {
332 if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
333 if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
334 }
335 mCursor = newCursor;
336 if (newCursor != null) {
337 if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
338 if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
339 mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800340 mDataValid = true;
341 // notify the observers about the new cursor
342 notifyDataSetChanged();
343 } else {
344 mRowIDColumn = -1;
345 mDataValid = false;
346 // notify the observers about the lack of a data set
347 notifyDataSetInvalidated();
348 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800349 return oldCursor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800350 }
351
352 /**
353 * <p>Converts the cursor into a CharSequence. Subclasses should override this
354 * method to convert their results. The default implementation returns an
355 * empty String for null values or the default String representation of
356 * the value.</p>
357 *
358 * @param cursor the cursor to convert to a CharSequence
359 * @return a CharSequence representing the value
360 */
361 public CharSequence convertToString(Cursor cursor) {
362 return cursor == null ? "" : cursor.toString();
363 }
364
365 /**
366 * Runs a query with the specified constraint. This query is requested
367 * by the filter attached to this adapter.
368 *
369 * The query is provided by a
370 * {@link android.widget.FilterQueryProvider}.
371 * If no provider is specified, the current cursor is not filtered and returned.
372 *
373 * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
374 * and the previous cursor is closed.
375 *
376 * This method is always executed on a background thread, not on the
377 * application's main thread (or UI thread.)
378 *
379 * Contract: when constraint is null or empty, the original results,
380 * prior to any filtering, must be returned.
381 *
382 * @param constraint the constraint with which the query must be filtered
383 *
384 * @return a Cursor representing the results of the new query
385 *
386 * @see #getFilter()
387 * @see #getFilterQueryProvider()
388 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
389 */
390 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
391 if (mFilterQueryProvider != null) {
392 return mFilterQueryProvider.runQuery(constraint);
393 }
394
395 return mCursor;
396 }
397
398 public Filter getFilter() {
399 if (mCursorFilter == null) {
400 mCursorFilter = new CursorFilter(this);
401 }
402 return mCursorFilter;
403 }
404
405 /**
406 * Returns the query filter provider used for filtering. When the
407 * provider is null, no filtering occurs.
408 *
409 * @return the current filter query provider or null if it does not exist
410 *
411 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
412 * @see #runQueryOnBackgroundThread(CharSequence)
413 */
414 public FilterQueryProvider getFilterQueryProvider() {
415 return mFilterQueryProvider;
416 }
417
418 /**
419 * Sets the query filter provider used to filter the current Cursor.
420 * The provider's
421 * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
422 * method is invoked when filtering is requested by a client of
423 * this adapter.
424 *
425 * @param filterQueryProvider the filter query provider or null to remove it
426 *
427 * @see #getFilterQueryProvider()
428 * @see #runQueryOnBackgroundThread(CharSequence)
429 */
430 public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
431 mFilterQueryProvider = filterQueryProvider;
432 }
433
434 /**
435 * Called when the {@link ContentObserver} on the cursor receives a change notification.
436 * The default implementation provides the auto-requery logic, but may be overridden by
437 * sub classes.
438 *
439 * @see ContentObserver#onChange(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800440 */
441 protected void onContentChanged() {
442 if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
443 if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
444 mDataValid = mCursor.requery();
445 }
446 }
447
448 private class ChangeObserver extends ContentObserver {
449 public ChangeObserver() {
450 super(new Handler());
451 }
452
453 @Override
454 public boolean deliverSelfNotifications() {
455 return true;
456 }
457
458 @Override
459 public void onChange(boolean selfChange) {
460 onContentChanged();
461 }
462 }
463
464 private class MyDataSetObserver extends DataSetObserver {
465 @Override
466 public void onChanged() {
467 mDataValid = true;
468 notifyDataSetChanged();
469 }
470
471 @Override
472 public void onInvalidated() {
473 mDataValid = false;
474 notifyDataSetInvalidated();
475 }
476 }
477
478}