blob: 8e318fd449e80b32b3d36f6227c5c6425fdffdf3 [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;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080020import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.database.ContentObserver;
22import android.database.Cursor;
23import android.database.DataSetObserver;
24import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.util.Log;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080026import android.view.ContextThemeWrapper;
27import android.view.LayoutInflater;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.view.View;
29import android.view.ViewGroup;
30
31/**
Alan Viverette647e6bd2013-09-26 14:38:14 -070032 * Adapter that exposes data from a {@link android.database.Cursor Cursor} to a
33 * {@link android.widget.ListView ListView} widget.
34 * <p>
35 * The Cursor must include a column named "_id" or this class will not work.
36 * Additionally, using {@link android.database.MergeCursor} with this class will
37 * not work if the merged Cursors have overlapping values in their "_id"
38 * columns.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039 */
40public abstract class CursorAdapter extends BaseAdapter implements Filterable,
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080041 CursorFilter.CursorFilterClient, Spinner.ThemedSpinnerAdapter {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042 /**
43 * This field should be made private, so it is hidden from the SDK.
44 * {@hide}
45 */
46 protected boolean mDataValid;
47 /**
48 * This field should be made private, so it is hidden from the SDK.
49 * {@hide}
50 */
51 protected boolean mAutoRequery;
52 /**
53 * This field should be made private, so it is hidden from the SDK.
54 * {@hide}
55 */
56 protected Cursor mCursor;
57 /**
58 * This field should be made private, so it is hidden from the SDK.
59 * {@hide}
60 */
61 protected Context mContext;
62 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080063 * Context used for {@link #getDropDownView(int, View, ViewGroup)}.
64 * {@hide}
65 */
66 protected Context mDropDownContext;
67 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068 * This field should be made private, so it is hidden from the SDK.
69 * {@hide}
70 */
71 protected int mRowIDColumn;
72 /**
73 * This field should be made private, so it is hidden from the SDK.
74 * {@hide}
75 */
76 protected ChangeObserver mChangeObserver;
77 /**
78 * This field should be made private, so it is hidden from the SDK.
79 * {@hide}
80 */
Dianne Hackbornc9189352010-12-15 14:57:25 -080081 protected DataSetObserver mDataSetObserver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 /**
83 * This field should be made private, so it is hidden from the SDK.
84 * {@hide}
85 */
86 protected CursorFilter mCursorFilter;
87 /**
88 * This field should be made private, so it is hidden from the SDK.
89 * {@hide}
90 */
91 protected FilterQueryProvider mFilterQueryProvider;
92
93 /**
Jeff Hamilton451513b2010-03-31 23:54:11 -050094 * If set the adapter will call requery() on the cursor whenever a content change
Dianne Hackbornc9189352010-12-15 14:57:25 -080095 * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
96 *
97 * @deprecated This option is discouraged, as it results in Cursor queries
98 * being performed on the application's UI thread and thus can cause poor
99 * responsiveness or even Application Not Responding errors. As an alternative,
100 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
Jeff Hamilton451513b2010-03-31 23:54:11 -0500101 */
Dianne Hackbornc9189352010-12-15 14:57:25 -0800102 @Deprecated
Jeff Hamilton451513b2010-03-31 23:54:11 -0500103 public static final int FLAG_AUTO_REQUERY = 0x01;
104
105 /**
106 * If set the adapter will register a content observer on the cursor and will call
Dianne Hackbornc9189352010-12-15 14:57:25 -0800107 * {@link #onContentChanged()} when a notification comes in. Be careful when
108 * using this flag: you will need to unset the current Cursor from the adapter
109 * to avoid leaks due to its registered observers. This flag is not needed
110 * when using a CursorAdapter with a
111 * {@link android.content.CursorLoader}.
Jeff Hamilton451513b2010-03-31 23:54:11 -0500112 */
113 public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
114
115 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800116 * Constructor that always enables auto-requery.
117 *
118 * @deprecated This option is discouraged, as it results in Cursor queries
119 * being performed on the application's UI thread and thus can cause poor
120 * responsiveness or even Application Not Responding errors. As an alternative,
121 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122 *
123 * @param c The cursor from which to get the data.
124 * @param context The context
125 */
Dianne Hackbornc9189352010-12-15 14:57:25 -0800126 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127 public CursorAdapter(Context context, Cursor c) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500128 init(context, c, FLAG_AUTO_REQUERY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 }
130
131 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800132 * Constructor that allows control over auto-requery. It is recommended
133 * you not use this, but instead {@link #CursorAdapter(Context, Cursor, int)}.
134 * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
135 * will always be set.
136 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800137 * @param c The cursor from which to get the data.
138 * @param context The context
139 * @param autoRequery If true the adapter will call requery() on the
140 * cursor whenever it changes so the most recent
Dianne Hackbornc9189352010-12-15 14:57:25 -0800141 * data is always displayed. Using true here is discouraged.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 */
143 public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
Jeff Hamilton69e87f92010-04-22 11:26:00 -0500144 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
Jeff Hamilton451513b2010-03-31 23:54:11 -0500145 }
146
147 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800148 * Recommended constructor.
149 *
Jeff Hamilton451513b2010-03-31 23:54:11 -0500150 * @param c The cursor from which to get the data.
151 * @param context The context
Dianne Hackbornc9189352010-12-15 14:57:25 -0800152 * @param flags Flags used to determine the behavior of the adapter; may
153 * be any combination of {@link #FLAG_AUTO_REQUERY} and
154 * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
Jeff Hamilton451513b2010-03-31 23:54:11 -0500155 */
156 public CursorAdapter(Context context, Cursor c, int flags) {
157 init(context, c, flags);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158 }
159
Dianne Hackbornc9189352010-12-15 14:57:25 -0800160 /**
161 * @deprecated Don't use this, use the normal constructor. This will
162 * be removed in the future.
163 */
164 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 protected void init(Context context, Cursor c, boolean autoRequery) {
Jeff Hamilton69e87f92010-04-22 11:26:00 -0500166 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
Jeff Hamilton451513b2010-03-31 23:54:11 -0500167 }
168
Dianne Hackbornc9189352010-12-15 14:57:25 -0800169 void init(Context context, Cursor c, int flags) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500170 if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
171 flags |= FLAG_REGISTER_CONTENT_OBSERVER;
172 mAutoRequery = true;
173 } else {
174 mAutoRequery = false;
175 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800176 boolean cursorPresent = c != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 mCursor = c;
178 mDataValid = cursorPresent;
179 mContext = context;
180 mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
Jeff Hamilton451513b2010-03-31 23:54:11 -0500181 if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
182 mChangeObserver = new ChangeObserver();
Dianne Hackbornc9189352010-12-15 14:57:25 -0800183 mDataSetObserver = new MyDataSetObserver();
Jeff Hamilton451513b2010-03-31 23:54:11 -0500184 } else {
185 mChangeObserver = null;
Dianne Hackbornc9189352010-12-15 14:57:25 -0800186 mDataSetObserver = null;
Jeff Hamilton451513b2010-03-31 23:54:11 -0500187 }
188
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189 if (cursorPresent) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500190 if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
Dianne Hackbornc9189352010-12-15 14:57:25 -0800191 if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192 }
193 }
194
195 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800196 * Sets the {@link Resources.Theme} against which drop-down views are
197 * inflated.
198 * <p>
199 * By default, drop-down views are inflated against the theme of the
200 * {@link Context} passed to the adapter's constructor.
201 *
202 * @param theme the theme against which to inflate drop-down views or
203 * {@code null} to use the theme from the adapter's context
204 * @see #newDropDownView(Context, Cursor, ViewGroup)
205 */
206 @Override
207 public void setDropDownViewTheme(Resources.Theme theme) {
208 if (theme == null) {
209 mDropDownContext = null;
210 } else if (theme == mContext.getTheme()) {
211 mDropDownContext = mContext;
212 } else {
213 mDropDownContext = new ContextThemeWrapper(mContext, theme);
214 }
215 }
216
217 @Override
218 public Resources.Theme getDropDownViewTheme() {
219 return mDropDownContext == null ? null : mDropDownContext.getTheme();
220 }
221
222 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223 * Returns the cursor.
224 * @return the cursor.
225 */
226 public Cursor getCursor() {
227 return mCursor;
228 }
229
230 /**
231 * @see android.widget.ListAdapter#getCount()
232 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700233 public int getCount() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234 if (mDataValid && mCursor != null) {
235 return mCursor.getCount();
236 } else {
237 return 0;
238 }
239 }
240
241 /**
242 * @see android.widget.ListAdapter#getItem(int)
243 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700244 public Object getItem(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 if (mDataValid && mCursor != null) {
246 mCursor.moveToPosition(position);
247 return mCursor;
248 } else {
249 return null;
250 }
251 }
252
253 /**
254 * @see android.widget.ListAdapter#getItemId(int)
255 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700256 public long getItemId(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800257 if (mDataValid && mCursor != null) {
258 if (mCursor.moveToPosition(position)) {
259 return mCursor.getLong(mRowIDColumn);
260 } else {
261 return 0;
262 }
263 } else {
264 return 0;
265 }
266 }
267
268 @Override
269 public boolean hasStableIds() {
270 return true;
271 }
272
273 /**
274 * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
275 */
276 public View getView(int position, View convertView, ViewGroup parent) {
277 if (!mDataValid) {
278 throw new IllegalStateException("this should only be called when the cursor is valid");
279 }
280 if (!mCursor.moveToPosition(position)) {
281 throw new IllegalStateException("couldn't move cursor to position " + position);
282 }
283 View v;
284 if (convertView == null) {
285 v = newView(mContext, mCursor, parent);
286 } else {
287 v = convertView;
288 }
289 bindView(v, mContext, mCursor);
290 return v;
291 }
292
293 @Override
294 public View getDropDownView(int position, View convertView, ViewGroup parent) {
295 if (mDataValid) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800296 final Context context = mDropDownContext == null ? mContext : mDropDownContext;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 mCursor.moveToPosition(position);
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800298 final View v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800299 if (convertView == null) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800300 v = newDropDownView(context, mCursor, parent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800301 } else {
302 v = convertView;
303 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800304 bindView(v, context, mCursor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800305 return v;
306 } else {
307 return null;
308 }
309 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800310
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800311 /**
312 * Makes a new view to hold the data pointed to by cursor.
313 * @param context Interface to application's global information
314 * @param cursor The cursor from which to get the data. The cursor is already
315 * moved to the correct position.
316 * @param parent The parent to which the new view is attached to
317 * @return the newly created view.
318 */
319 public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
320
321 /**
322 * Makes a new drop down view to hold the data pointed to by cursor.
323 * @param context Interface to application's global information
324 * @param cursor The cursor from which to get the data. The cursor is already
325 * moved to the correct position.
326 * @param parent The parent to which the new view is attached to
327 * @return the newly created view.
328 */
329 public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
330 return newView(context, cursor, parent);
331 }
332
333 /**
334 * Bind an existing view to the data pointed to by cursor
335 * @param view Existing view, returned earlier by newView
336 * @param context Interface to application's global information
337 * @param cursor The cursor from which to get the data. The cursor is already
338 * moved to the correct position.
339 */
340 public abstract void bindView(View view, Context context, Cursor cursor);
341
342 /**
343 * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
344 * closed.
345 *
Dianne Hackbornc9189352010-12-15 14:57:25 -0800346 * @param cursor The new cursor to be used
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800347 */
348 public void changeCursor(Cursor cursor) {
Dianne Hackbornc9189352010-12-15 14:57:25 -0800349 Cursor old = swapCursor(cursor);
350 if (old != null) {
351 old.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800353 }
354
355 /**
356 * Swap in a new Cursor, returning the old Cursor. Unlike
357 * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
358 * closed.
359 *
360 * @param newCursor The new cursor to be used.
361 * @return Returns the previously set Cursor, or null if there wasa not one.
362 * If the given new Cursor is the same instance is the previously set
363 * Cursor, null is also returned.
364 */
365 public Cursor swapCursor(Cursor newCursor) {
366 if (newCursor == mCursor) {
367 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800369 Cursor oldCursor = mCursor;
370 if (oldCursor != null) {
371 if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
372 if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
373 }
374 mCursor = newCursor;
375 if (newCursor != null) {
376 if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
377 if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
378 mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 mDataValid = true;
380 // notify the observers about the new cursor
381 notifyDataSetChanged();
382 } else {
383 mRowIDColumn = -1;
384 mDataValid = false;
385 // notify the observers about the lack of a data set
386 notifyDataSetInvalidated();
387 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800388 return oldCursor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389 }
390
391 /**
392 * <p>Converts the cursor into a CharSequence. Subclasses should override this
393 * method to convert their results. The default implementation returns an
394 * empty String for null values or the default String representation of
395 * the value.</p>
396 *
397 * @param cursor the cursor to convert to a CharSequence
398 * @return a CharSequence representing the value
399 */
400 public CharSequence convertToString(Cursor cursor) {
401 return cursor == null ? "" : cursor.toString();
402 }
403
404 /**
405 * Runs a query with the specified constraint. This query is requested
406 * by the filter attached to this adapter.
407 *
408 * The query is provided by a
409 * {@link android.widget.FilterQueryProvider}.
410 * If no provider is specified, the current cursor is not filtered and returned.
411 *
412 * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
413 * and the previous cursor is closed.
414 *
415 * This method is always executed on a background thread, not on the
416 * application's main thread (or UI thread.)
417 *
418 * Contract: when constraint is null or empty, the original results,
419 * prior to any filtering, must be returned.
420 *
421 * @param constraint the constraint with which the query must be filtered
422 *
423 * @return a Cursor representing the results of the new query
424 *
425 * @see #getFilter()
426 * @see #getFilterQueryProvider()
427 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
428 */
429 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
430 if (mFilterQueryProvider != null) {
431 return mFilterQueryProvider.runQuery(constraint);
432 }
433
434 return mCursor;
435 }
436
437 public Filter getFilter() {
438 if (mCursorFilter == null) {
439 mCursorFilter = new CursorFilter(this);
440 }
441 return mCursorFilter;
442 }
443
444 /**
445 * Returns the query filter provider used for filtering. When the
446 * provider is null, no filtering occurs.
447 *
448 * @return the current filter query provider or null if it does not exist
449 *
450 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
451 * @see #runQueryOnBackgroundThread(CharSequence)
452 */
453 public FilterQueryProvider getFilterQueryProvider() {
454 return mFilterQueryProvider;
455 }
456
457 /**
458 * Sets the query filter provider used to filter the current Cursor.
459 * The provider's
460 * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
461 * method is invoked when filtering is requested by a client of
462 * this adapter.
463 *
464 * @param filterQueryProvider the filter query provider or null to remove it
465 *
466 * @see #getFilterQueryProvider()
467 * @see #runQueryOnBackgroundThread(CharSequence)
468 */
469 public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
470 mFilterQueryProvider = filterQueryProvider;
471 }
472
473 /**
474 * Called when the {@link ContentObserver} on the cursor receives a change notification.
475 * The default implementation provides the auto-requery logic, but may be overridden by
476 * sub classes.
477 *
478 * @see ContentObserver#onChange(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 */
480 protected void onContentChanged() {
481 if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
Joe Onorato43a17652011-04-06 19:22:23 -0700482 if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800483 mDataValid = mCursor.requery();
484 }
485 }
486
487 private class ChangeObserver extends ContentObserver {
488 public ChangeObserver() {
489 super(new Handler());
490 }
491
492 @Override
493 public boolean deliverSelfNotifications() {
494 return true;
495 }
496
497 @Override
498 public void onChange(boolean selfChange) {
499 onContentChanged();
500 }
501 }
502
503 private class MyDataSetObserver extends DataSetObserver {
504 @Override
505 public void onChanged() {
506 mDataValid = true;
507 notifyDataSetChanged();
508 }
509
510 @Override
511 public void onInvalidated() {
512 mDataValid = false;
513 notifyDataSetInvalidated();
514 }
515 }
516
517}