blob: cc8b55009316ea8776904e1e60fbcbb465c07fd0 [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
Mathew Inwooda85f4eb2018-08-21 16:08:34 +010019import android.annotation.UnsupportedAppUsage;
Tor Norbye83c68962015-03-10 20:55:31 -070020import android.annotation.WorkerThread;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.Context;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080022import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.database.ContentObserver;
24import android.database.Cursor;
25import android.database.DataSetObserver;
26import android.os.Handler;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.util.Log;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080028import android.view.ContextThemeWrapper;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.view.View;
30import android.view.ViewGroup;
31
32/**
Alan Viverette647e6bd2013-09-26 14:38:14 -070033 * Adapter that exposes data from a {@link android.database.Cursor Cursor} to a
34 * {@link android.widget.ListView ListView} widget.
35 * <p>
36 * The Cursor must include a column named "_id" or this class will not work.
37 * Additionally, using {@link android.database.MergeCursor} with this class will
38 * not work if the merged Cursors have overlapping values in their "_id"
39 * columns.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040 */
41public abstract class CursorAdapter extends BaseAdapter implements Filterable,
Alan Viverette2add9bc2015-06-02 14:54:40 -070042 CursorFilter.CursorFilterClient, ThemedSpinnerAdapter {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043 /**
44 * This field should be made private, so it is hidden from the SDK.
45 * {@hide}
46 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +010047 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048 protected boolean mDataValid;
49 /**
50 * This field should be made private, so it is hidden from the SDK.
51 * {@hide}
52 */
53 protected boolean mAutoRequery;
54 /**
55 * This field should be made private, so it is hidden from the SDK.
56 * {@hide}
57 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +010058 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059 protected Cursor mCursor;
60 /**
61 * This field should be made private, so it is hidden from the SDK.
62 * {@hide}
63 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +010064 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 protected Context mContext;
66 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080067 * Context used for {@link #getDropDownView(int, View, ViewGroup)}.
68 * {@hide}
69 */
70 protected Context mDropDownContext;
71 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072 * This field should be made private, so it is hidden from the SDK.
73 * {@hide}
74 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +010075 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076 protected int mRowIDColumn;
77 /**
78 * This field should be made private, so it is hidden from the SDK.
79 * {@hide}
80 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +010081 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080082 protected ChangeObserver mChangeObserver;
83 /**
84 * This field should be made private, so it is hidden from the SDK.
85 * {@hide}
86 */
Mathew Inwooda85f4eb2018-08-21 16:08:34 +010087 @UnsupportedAppUsage
Dianne Hackbornc9189352010-12-15 14:57:25 -080088 protected DataSetObserver mDataSetObserver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 /**
90 * This field should be made private, so it is hidden from the SDK.
91 * {@hide}
92 */
93 protected CursorFilter mCursorFilter;
94 /**
95 * This field should be made private, so it is hidden from the SDK.
96 * {@hide}
97 */
98 protected FilterQueryProvider mFilterQueryProvider;
99
100 /**
Jeff Hamilton451513b2010-03-31 23:54:11 -0500101 * If set the adapter will call requery() on the cursor whenever a content change
Dianne Hackbornc9189352010-12-15 14:57:25 -0800102 * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
103 *
104 * @deprecated This option is discouraged, as it results in Cursor queries
105 * being performed on the application's UI thread and thus can cause poor
106 * responsiveness or even Application Not Responding errors. As an alternative,
107 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
Jeff Hamilton451513b2010-03-31 23:54:11 -0500108 */
Dianne Hackbornc9189352010-12-15 14:57:25 -0800109 @Deprecated
Jeff Hamilton451513b2010-03-31 23:54:11 -0500110 public static final int FLAG_AUTO_REQUERY = 0x01;
111
112 /**
113 * If set the adapter will register a content observer on the cursor and will call
Dianne Hackbornc9189352010-12-15 14:57:25 -0800114 * {@link #onContentChanged()} when a notification comes in. Be careful when
115 * using this flag: you will need to unset the current Cursor from the adapter
116 * to avoid leaks due to its registered observers. This flag is not needed
117 * when using a CursorAdapter with a
118 * {@link android.content.CursorLoader}.
Jeff Hamilton451513b2010-03-31 23:54:11 -0500119 */
120 public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
121
122 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800123 * Constructor that always enables auto-requery.
124 *
125 * @deprecated This option is discouraged, as it results in Cursor queries
126 * being performed on the application's UI thread and thus can cause poor
127 * responsiveness or even Application Not Responding errors. As an alternative,
128 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 *
130 * @param c The cursor from which to get the data.
131 * @param context The context
132 */
Dianne Hackbornc9189352010-12-15 14:57:25 -0800133 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134 public CursorAdapter(Context context, Cursor c) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500135 init(context, c, FLAG_AUTO_REQUERY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800136 }
137
138 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800139 * Constructor that allows control over auto-requery. It is recommended
140 * you not use this, but instead {@link #CursorAdapter(Context, Cursor, int)}.
141 * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
142 * will always be set.
143 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 * @param c The cursor from which to get the data.
145 * @param context The context
146 * @param autoRequery If true the adapter will call requery() on the
147 * cursor whenever it changes so the most recent
Dianne Hackbornc9189352010-12-15 14:57:25 -0800148 * data is always displayed. Using true here is discouraged.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 */
150 public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
Jeff Hamilton69e87f92010-04-22 11:26:00 -0500151 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
Jeff Hamilton451513b2010-03-31 23:54:11 -0500152 }
153
154 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800155 * Recommended constructor.
156 *
Jeff Hamilton451513b2010-03-31 23:54:11 -0500157 * @param c The cursor from which to get the data.
158 * @param context The context
Dianne Hackbornc9189352010-12-15 14:57:25 -0800159 * @param flags Flags used to determine the behavior of the adapter; may
160 * be any combination of {@link #FLAG_AUTO_REQUERY} and
161 * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
Jeff Hamilton451513b2010-03-31 23:54:11 -0500162 */
163 public CursorAdapter(Context context, Cursor c, int flags) {
164 init(context, c, flags);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 }
166
Dianne Hackbornc9189352010-12-15 14:57:25 -0800167 /**
168 * @deprecated Don't use this, use the normal constructor. This will
169 * be removed in the future.
170 */
171 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172 protected void init(Context context, Cursor c, boolean autoRequery) {
Jeff Hamilton69e87f92010-04-22 11:26:00 -0500173 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
Jeff Hamilton451513b2010-03-31 23:54:11 -0500174 }
175
Dianne Hackbornc9189352010-12-15 14:57:25 -0800176 void init(Context context, Cursor c, int flags) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500177 if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
178 flags |= FLAG_REGISTER_CONTENT_OBSERVER;
179 mAutoRequery = true;
180 } else {
181 mAutoRequery = false;
182 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 boolean cursorPresent = c != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800184 mCursor = c;
185 mDataValid = cursorPresent;
186 mContext = context;
187 mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
Jeff Hamilton451513b2010-03-31 23:54:11 -0500188 if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
189 mChangeObserver = new ChangeObserver();
Dianne Hackbornc9189352010-12-15 14:57:25 -0800190 mDataSetObserver = new MyDataSetObserver();
Jeff Hamilton451513b2010-03-31 23:54:11 -0500191 } else {
192 mChangeObserver = null;
Dianne Hackbornc9189352010-12-15 14:57:25 -0800193 mDataSetObserver = null;
Jeff Hamilton451513b2010-03-31 23:54:11 -0500194 }
195
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800196 if (cursorPresent) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500197 if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
Dianne Hackbornc9189352010-12-15 14:57:25 -0800198 if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 }
200 }
201
202 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800203 * Sets the {@link Resources.Theme} against which drop-down views are
204 * inflated.
205 * <p>
206 * By default, drop-down views are inflated against the theme of the
207 * {@link Context} passed to the adapter's constructor.
208 *
209 * @param theme the theme against which to inflate drop-down views or
210 * {@code null} to use the theme from the adapter's context
211 * @see #newDropDownView(Context, Cursor, ViewGroup)
212 */
213 @Override
214 public void setDropDownViewTheme(Resources.Theme theme) {
215 if (theme == null) {
216 mDropDownContext = null;
217 } else if (theme == mContext.getTheme()) {
218 mDropDownContext = mContext;
219 } else {
220 mDropDownContext = new ContextThemeWrapper(mContext, theme);
221 }
222 }
223
224 @Override
225 public Resources.Theme getDropDownViewTheme() {
226 return mDropDownContext == null ? null : mDropDownContext.getTheme();
227 }
228
229 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 * Returns the cursor.
231 * @return the cursor.
232 */
233 public Cursor getCursor() {
234 return mCursor;
235 }
236
237 /**
238 * @see android.widget.ListAdapter#getCount()
239 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700240 public int getCount() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 if (mDataValid && mCursor != null) {
242 return mCursor.getCount();
243 } else {
244 return 0;
245 }
246 }
247
248 /**
249 * @see android.widget.ListAdapter#getItem(int)
250 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700251 public Object getItem(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 if (mDataValid && mCursor != null) {
253 mCursor.moveToPosition(position);
254 return mCursor;
255 } else {
256 return null;
257 }
258 }
259
260 /**
261 * @see android.widget.ListAdapter#getItemId(int)
262 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700263 public long getItemId(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800264 if (mDataValid && mCursor != null) {
265 if (mCursor.moveToPosition(position)) {
266 return mCursor.getLong(mRowIDColumn);
267 } else {
268 return 0;
269 }
270 } else {
271 return 0;
272 }
273 }
274
275 @Override
276 public boolean hasStableIds() {
277 return true;
278 }
279
280 /**
281 * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
282 */
283 public View getView(int position, View convertView, ViewGroup parent) {
284 if (!mDataValid) {
285 throw new IllegalStateException("this should only be called when the cursor is valid");
286 }
287 if (!mCursor.moveToPosition(position)) {
288 throw new IllegalStateException("couldn't move cursor to position " + position);
289 }
290 View v;
291 if (convertView == null) {
292 v = newView(mContext, mCursor, parent);
293 } else {
294 v = convertView;
295 }
296 bindView(v, mContext, mCursor);
297 return v;
298 }
299
300 @Override
301 public View getDropDownView(int position, View convertView, ViewGroup parent) {
302 if (mDataValid) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800303 final Context context = mDropDownContext == null ? mContext : mDropDownContext;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800304 mCursor.moveToPosition(position);
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800305 final View v;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 if (convertView == null) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800307 v = newDropDownView(context, mCursor, parent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800308 } else {
309 v = convertView;
310 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800311 bindView(v, context, mCursor);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800312 return v;
313 } else {
314 return null;
315 }
316 }
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800317
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800318 /**
319 * Makes a new view to hold the data pointed to by cursor.
320 * @param context Interface to application's global information
321 * @param cursor The cursor from which to get the data. The cursor is already
322 * moved to the correct position.
323 * @param parent The parent to which the new view is attached to
324 * @return the newly created view.
325 */
326 public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
327
328 /**
329 * Makes a new drop down view to hold the data pointed to by cursor.
330 * @param context Interface to application's global information
331 * @param cursor The cursor from which to get the data. The cursor is already
332 * moved to the correct position.
333 * @param parent The parent to which the new view is attached to
334 * @return the newly created view.
335 */
336 public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
337 return newView(context, cursor, parent);
338 }
339
340 /**
341 * Bind an existing view to the data pointed to by cursor
342 * @param view Existing view, returned earlier by newView
343 * @param context Interface to application's global information
344 * @param cursor The cursor from which to get the data. The cursor is already
345 * moved to the correct position.
346 */
347 public abstract void bindView(View view, Context context, Cursor cursor);
348
349 /**
350 * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
351 * closed.
352 *
Dianne Hackbornc9189352010-12-15 14:57:25 -0800353 * @param cursor The new cursor to be used
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354 */
355 public void changeCursor(Cursor cursor) {
Dianne Hackbornc9189352010-12-15 14:57:25 -0800356 Cursor old = swapCursor(cursor);
357 if (old != null) {
358 old.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800359 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800360 }
361
362 /**
363 * Swap in a new Cursor, returning the old Cursor. Unlike
364 * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
365 * closed.
366 *
367 * @param newCursor The new cursor to be used.
368 * @return Returns the previously set Cursor, or null if there wasa not one.
369 * If the given new Cursor is the same instance is the previously set
370 * Cursor, null is also returned.
371 */
372 public Cursor swapCursor(Cursor newCursor) {
373 if (newCursor == mCursor) {
374 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800375 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800376 Cursor oldCursor = mCursor;
377 if (oldCursor != null) {
378 if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
379 if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
380 }
381 mCursor = newCursor;
382 if (newCursor != null) {
383 if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
384 if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
385 mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800386 mDataValid = true;
387 // notify the observers about the new cursor
388 notifyDataSetChanged();
389 } else {
390 mRowIDColumn = -1;
391 mDataValid = false;
392 // notify the observers about the lack of a data set
393 notifyDataSetInvalidated();
394 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800395 return oldCursor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800396 }
397
398 /**
399 * <p>Converts the cursor into a CharSequence. Subclasses should override this
400 * method to convert their results. The default implementation returns an
401 * empty String for null values or the default String representation of
402 * the value.</p>
403 *
404 * @param cursor the cursor to convert to a CharSequence
405 * @return a CharSequence representing the value
406 */
407 public CharSequence convertToString(Cursor cursor) {
408 return cursor == null ? "" : cursor.toString();
409 }
410
411 /**
412 * Runs a query with the specified constraint. This query is requested
413 * by the filter attached to this adapter.
414 *
415 * The query is provided by a
416 * {@link android.widget.FilterQueryProvider}.
417 * If no provider is specified, the current cursor is not filtered and returned.
418 *
419 * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
420 * and the previous cursor is closed.
421 *
422 * This method is always executed on a background thread, not on the
423 * application's main thread (or UI thread.)
424 *
425 * Contract: when constraint is null or empty, the original results,
426 * prior to any filtering, must be returned.
427 *
428 * @param constraint the constraint with which the query must be filtered
429 *
430 * @return a Cursor representing the results of the new query
431 *
432 * @see #getFilter()
433 * @see #getFilterQueryProvider()
434 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
435 */
Tor Norbye83c68962015-03-10 20:55:31 -0700436 @WorkerThread
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
438 if (mFilterQueryProvider != null) {
439 return mFilterQueryProvider.runQuery(constraint);
440 }
441
442 return mCursor;
443 }
444
445 public Filter getFilter() {
446 if (mCursorFilter == null) {
447 mCursorFilter = new CursorFilter(this);
448 }
449 return mCursorFilter;
450 }
451
452 /**
453 * Returns the query filter provider used for filtering. When the
454 * provider is null, no filtering occurs.
455 *
456 * @return the current filter query provider or null if it does not exist
457 *
458 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
459 * @see #runQueryOnBackgroundThread(CharSequence)
460 */
461 public FilterQueryProvider getFilterQueryProvider() {
462 return mFilterQueryProvider;
463 }
464
465 /**
466 * Sets the query filter provider used to filter the current Cursor.
467 * The provider's
468 * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
469 * method is invoked when filtering is requested by a client of
470 * this adapter.
471 *
472 * @param filterQueryProvider the filter query provider or null to remove it
473 *
474 * @see #getFilterQueryProvider()
475 * @see #runQueryOnBackgroundThread(CharSequence)
476 */
477 public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
478 mFilterQueryProvider = filterQueryProvider;
479 }
480
481 /**
482 * Called when the {@link ContentObserver} on the cursor receives a change notification.
483 * The default implementation provides the auto-requery logic, but may be overridden by
484 * sub classes.
485 *
486 * @see ContentObserver#onChange(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800487 */
488 protected void onContentChanged() {
489 if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
Joe Onorato43a17652011-04-06 19:22:23 -0700490 if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 mDataValid = mCursor.requery();
492 }
493 }
494
495 private class ChangeObserver extends ContentObserver {
496 public ChangeObserver() {
497 super(new Handler());
498 }
499
500 @Override
501 public boolean deliverSelfNotifications() {
502 return true;
503 }
504
505 @Override
506 public void onChange(boolean selfChange) {
507 onContentChanged();
508 }
509 }
510
511 private class MyDataSetObserver extends DataSetObserver {
512 @Override
513 public void onChanged() {
514 mDataValid = true;
515 notifyDataSetChanged();
516 }
517
518 @Override
519 public void onInvalidated() {
520 mDataValid = false;
521 notifyDataSetInvalidated();
522 }
523 }
524
525}