blob: d4c5be0615a727bf1bf44e26dd3a35abc24ef8e8 [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.util.Log;
25import android.view.View;
26import android.view.ViewGroup;
27
28/**
Alan Viverette647e6bd2013-09-26 14:38:14 -070029 * Adapter that exposes data from a {@link android.database.Cursor Cursor} to a
30 * {@link android.widget.ListView ListView} widget.
31 * <p>
32 * The Cursor must include a column named "_id" or this class will not work.
33 * Additionally, using {@link android.database.MergeCursor} with this class will
34 * not work if the merged Cursors have overlapping values in their "_id"
35 * columns.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036 */
37public abstract class CursorAdapter extends BaseAdapter implements Filterable,
38 CursorFilter.CursorFilterClient {
39 /**
40 * This field should be made private, so it is hidden from the SDK.
41 * {@hide}
42 */
43 protected boolean mDataValid;
44 /**
45 * This field should be made private, so it is hidden from the SDK.
46 * {@hide}
47 */
48 protected boolean mAutoRequery;
49 /**
50 * This field should be made private, so it is hidden from the SDK.
51 * {@hide}
52 */
53 protected Cursor mCursor;
54 /**
55 * This field should be made private, so it is hidden from the SDK.
56 * {@hide}
57 */
58 protected Context mContext;
59 /**
60 * This field should be made private, so it is hidden from the SDK.
61 * {@hide}
62 */
63 protected int mRowIDColumn;
64 /**
65 * This field should be made private, so it is hidden from the SDK.
66 * {@hide}
67 */
68 protected ChangeObserver mChangeObserver;
69 /**
70 * This field should be made private, so it is hidden from the SDK.
71 * {@hide}
72 */
Dianne Hackbornc9189352010-12-15 14:57:25 -080073 protected DataSetObserver mDataSetObserver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074 /**
75 * This field should be made private, so it is hidden from the SDK.
76 * {@hide}
77 */
78 protected CursorFilter mCursorFilter;
79 /**
80 * This field should be made private, so it is hidden from the SDK.
81 * {@hide}
82 */
83 protected FilterQueryProvider mFilterQueryProvider;
84
85 /**
Jeff Hamilton451513b2010-03-31 23:54:11 -050086 * If set the adapter will call requery() on the cursor whenever a content change
Dianne Hackbornc9189352010-12-15 14:57:25 -080087 * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
88 *
89 * @deprecated This option is discouraged, as it results in Cursor queries
90 * being performed on the application's UI thread and thus can cause poor
91 * responsiveness or even Application Not Responding errors. As an alternative,
92 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
Jeff Hamilton451513b2010-03-31 23:54:11 -050093 */
Dianne Hackbornc9189352010-12-15 14:57:25 -080094 @Deprecated
Jeff Hamilton451513b2010-03-31 23:54:11 -050095 public static final int FLAG_AUTO_REQUERY = 0x01;
96
97 /**
98 * If set the adapter will register a content observer on the cursor and will call
Dianne Hackbornc9189352010-12-15 14:57:25 -080099 * {@link #onContentChanged()} when a notification comes in. Be careful when
100 * using this flag: you will need to unset the current Cursor from the adapter
101 * to avoid leaks due to its registered observers. This flag is not needed
102 * when using a CursorAdapter with a
103 * {@link android.content.CursorLoader}.
Jeff Hamilton451513b2010-03-31 23:54:11 -0500104 */
105 public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
106
107 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800108 * Constructor that always enables auto-requery.
109 *
110 * @deprecated This option is discouraged, as it results in Cursor queries
111 * being performed on the application's UI thread and thus can cause poor
112 * responsiveness or even Application Not Responding errors. As an alternative,
113 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 *
115 * @param c The cursor from which to get the data.
116 * @param context The context
117 */
Dianne Hackbornc9189352010-12-15 14:57:25 -0800118 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 public CursorAdapter(Context context, Cursor c) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500120 init(context, c, FLAG_AUTO_REQUERY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121 }
122
123 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800124 * Constructor that allows control over auto-requery. It is recommended
125 * you not use this, but instead {@link #CursorAdapter(Context, Cursor, int)}.
126 * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
127 * will always be set.
128 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 * @param c The cursor from which to get the data.
130 * @param context The context
131 * @param autoRequery If true the adapter will call requery() on the
132 * cursor whenever it changes so the most recent
Dianne Hackbornc9189352010-12-15 14:57:25 -0800133 * data is always displayed. Using true here is discouraged.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134 */
135 public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
Jeff Hamilton69e87f92010-04-22 11:26:00 -0500136 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
Jeff Hamilton451513b2010-03-31 23:54:11 -0500137 }
138
139 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800140 * Recommended constructor.
141 *
Jeff Hamilton451513b2010-03-31 23:54:11 -0500142 * @param c The cursor from which to get the data.
143 * @param context The context
Dianne Hackbornc9189352010-12-15 14:57:25 -0800144 * @param flags Flags used to determine the behavior of the adapter; may
145 * be any combination of {@link #FLAG_AUTO_REQUERY} and
146 * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
Jeff Hamilton451513b2010-03-31 23:54:11 -0500147 */
148 public CursorAdapter(Context context, Cursor c, int flags) {
149 init(context, c, flags);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800150 }
151
Dianne Hackbornc9189352010-12-15 14:57:25 -0800152 /**
153 * @deprecated Don't use this, use the normal constructor. This will
154 * be removed in the future.
155 */
156 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157 protected void init(Context context, Cursor c, boolean autoRequery) {
Jeff Hamilton69e87f92010-04-22 11:26:00 -0500158 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
Jeff Hamilton451513b2010-03-31 23:54:11 -0500159 }
160
Dianne Hackbornc9189352010-12-15 14:57:25 -0800161 void init(Context context, Cursor c, int flags) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500162 if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
163 flags |= FLAG_REGISTER_CONTENT_OBSERVER;
164 mAutoRequery = true;
165 } else {
166 mAutoRequery = false;
167 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 boolean cursorPresent = c != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169 mCursor = c;
170 mDataValid = cursorPresent;
171 mContext = context;
172 mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
Jeff Hamilton451513b2010-03-31 23:54:11 -0500173 if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
174 mChangeObserver = new ChangeObserver();
Dianne Hackbornc9189352010-12-15 14:57:25 -0800175 mDataSetObserver = new MyDataSetObserver();
Jeff Hamilton451513b2010-03-31 23:54:11 -0500176 } else {
177 mChangeObserver = null;
Dianne Hackbornc9189352010-12-15 14:57:25 -0800178 mDataSetObserver = null;
Jeff Hamilton451513b2010-03-31 23:54:11 -0500179 }
180
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 if (cursorPresent) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500182 if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
Dianne Hackbornc9189352010-12-15 14:57:25 -0800183 if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800184 }
185 }
186
187 /**
188 * Returns the cursor.
189 * @return the cursor.
190 */
191 public Cursor getCursor() {
192 return mCursor;
193 }
194
195 /**
196 * @see android.widget.ListAdapter#getCount()
197 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700198 public int getCount() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 if (mDataValid && mCursor != null) {
200 return mCursor.getCount();
201 } else {
202 return 0;
203 }
204 }
205
206 /**
207 * @see android.widget.ListAdapter#getItem(int)
208 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700209 public Object getItem(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 if (mDataValid && mCursor != null) {
211 mCursor.moveToPosition(position);
212 return mCursor;
213 } else {
214 return null;
215 }
216 }
217
218 /**
219 * @see android.widget.ListAdapter#getItemId(int)
220 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700221 public long getItemId(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 if (mDataValid && mCursor != null) {
223 if (mCursor.moveToPosition(position)) {
224 return mCursor.getLong(mRowIDColumn);
225 } else {
226 return 0;
227 }
228 } else {
229 return 0;
230 }
231 }
232
233 @Override
234 public boolean hasStableIds() {
235 return true;
236 }
237
238 /**
239 * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
240 */
241 public View getView(int position, View convertView, ViewGroup parent) {
242 if (!mDataValid) {
243 throw new IllegalStateException("this should only be called when the cursor is valid");
244 }
245 if (!mCursor.moveToPosition(position)) {
246 throw new IllegalStateException("couldn't move cursor to position " + position);
247 }
248 View v;
249 if (convertView == null) {
250 v = newView(mContext, mCursor, parent);
251 } else {
252 v = convertView;
253 }
254 bindView(v, mContext, mCursor);
255 return v;
256 }
257
258 @Override
259 public View getDropDownView(int position, View convertView, ViewGroup parent) {
260 if (mDataValid) {
261 mCursor.moveToPosition(position);
262 View v;
263 if (convertView == null) {
264 v = newDropDownView(mContext, mCursor, parent);
265 } else {
266 v = convertView;
267 }
268 bindView(v, mContext, mCursor);
269 return v;
270 } else {
271 return null;
272 }
273 }
274
275 /**
276 * Makes a new view to hold the data pointed to by cursor.
277 * @param context Interface to application's global information
278 * @param cursor The cursor from which to get the data. The cursor is already
279 * moved to the correct position.
280 * @param parent The parent to which the new view is attached to
281 * @return the newly created view.
282 */
283 public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
284
285 /**
286 * Makes a new drop down view to hold the data pointed to by cursor.
287 * @param context Interface to application's global information
288 * @param cursor The cursor from which to get the data. The cursor is already
289 * moved to the correct position.
290 * @param parent The parent to which the new view is attached to
291 * @return the newly created view.
292 */
293 public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
294 return newView(context, cursor, parent);
295 }
296
297 /**
298 * Bind an existing view to the data pointed to by cursor
299 * @param view Existing view, returned earlier by newView
300 * @param context Interface to application's global information
301 * @param cursor The cursor from which to get the data. The cursor is already
302 * moved to the correct position.
303 */
304 public abstract void bindView(View view, Context context, Cursor cursor);
305
306 /**
307 * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
308 * closed.
309 *
Dianne Hackbornc9189352010-12-15 14:57:25 -0800310 * @param cursor The new cursor to be used
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800311 */
312 public void changeCursor(Cursor cursor) {
Dianne Hackbornc9189352010-12-15 14:57:25 -0800313 Cursor old = swapCursor(cursor);
314 if (old != null) {
315 old.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800316 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800317 }
318
319 /**
320 * Swap in a new Cursor, returning the old Cursor. Unlike
321 * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
322 * closed.
323 *
324 * @param newCursor The new cursor to be used.
325 * @return Returns the previously set Cursor, or null if there wasa not one.
326 * If the given new Cursor is the same instance is the previously set
327 * Cursor, null is also returned.
328 */
329 public Cursor swapCursor(Cursor newCursor) {
330 if (newCursor == mCursor) {
331 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800332 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800333 Cursor oldCursor = mCursor;
334 if (oldCursor != null) {
335 if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
336 if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
337 }
338 mCursor = newCursor;
339 if (newCursor != null) {
340 if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
341 if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
342 mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800343 mDataValid = true;
344 // notify the observers about the new cursor
345 notifyDataSetChanged();
346 } else {
347 mRowIDColumn = -1;
348 mDataValid = false;
349 // notify the observers about the lack of a data set
350 notifyDataSetInvalidated();
351 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800352 return oldCursor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800353 }
354
355 /**
356 * <p>Converts the cursor into a CharSequence. Subclasses should override this
357 * method to convert their results. The default implementation returns an
358 * empty String for null values or the default String representation of
359 * the value.</p>
360 *
361 * @param cursor the cursor to convert to a CharSequence
362 * @return a CharSequence representing the value
363 */
364 public CharSequence convertToString(Cursor cursor) {
365 return cursor == null ? "" : cursor.toString();
366 }
367
368 /**
369 * Runs a query with the specified constraint. This query is requested
370 * by the filter attached to this adapter.
371 *
372 * The query is provided by a
373 * {@link android.widget.FilterQueryProvider}.
374 * If no provider is specified, the current cursor is not filtered and returned.
375 *
376 * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
377 * and the previous cursor is closed.
378 *
379 * This method is always executed on a background thread, not on the
380 * application's main thread (or UI thread.)
381 *
382 * Contract: when constraint is null or empty, the original results,
383 * prior to any filtering, must be returned.
384 *
385 * @param constraint the constraint with which the query must be filtered
386 *
387 * @return a Cursor representing the results of the new query
388 *
389 * @see #getFilter()
390 * @see #getFilterQueryProvider()
391 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
392 */
393 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
394 if (mFilterQueryProvider != null) {
395 return mFilterQueryProvider.runQuery(constraint);
396 }
397
398 return mCursor;
399 }
400
401 public Filter getFilter() {
402 if (mCursorFilter == null) {
403 mCursorFilter = new CursorFilter(this);
404 }
405 return mCursorFilter;
406 }
407
408 /**
409 * Returns the query filter provider used for filtering. When the
410 * provider is null, no filtering occurs.
411 *
412 * @return the current filter query provider or null if it does not exist
413 *
414 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
415 * @see #runQueryOnBackgroundThread(CharSequence)
416 */
417 public FilterQueryProvider getFilterQueryProvider() {
418 return mFilterQueryProvider;
419 }
420
421 /**
422 * Sets the query filter provider used to filter the current Cursor.
423 * The provider's
424 * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
425 * method is invoked when filtering is requested by a client of
426 * this adapter.
427 *
428 * @param filterQueryProvider the filter query provider or null to remove it
429 *
430 * @see #getFilterQueryProvider()
431 * @see #runQueryOnBackgroundThread(CharSequence)
432 */
433 public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
434 mFilterQueryProvider = filterQueryProvider;
435 }
436
437 /**
438 * Called when the {@link ContentObserver} on the cursor receives a change notification.
439 * The default implementation provides the auto-requery logic, but may be overridden by
440 * sub classes.
441 *
442 * @see ContentObserver#onChange(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800443 */
444 protected void onContentChanged() {
445 if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
Joe Onorato43a17652011-04-06 19:22:23 -0700446 if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447 mDataValid = mCursor.requery();
448 }
449 }
450
451 private class ChangeObserver extends ContentObserver {
452 public ChangeObserver() {
453 super(new Handler());
454 }
455
456 @Override
457 public boolean deliverSelfNotifications() {
458 return true;
459 }
460
461 @Override
462 public void onChange(boolean selfChange) {
463 onContentChanged();
464 }
465 }
466
467 private class MyDataSetObserver extends DataSetObserver {
468 @Override
469 public void onChanged() {
470 mDataValid = true;
471 notifyDataSetChanged();
472 }
473
474 @Override
475 public void onInvalidated() {
476 mDataValid = false;
477 notifyDataSetInvalidated();
478 }
479 }
480
481}