blob: 6c4c39d9456f048dd82800d7e1e6c43764f8f2f5 [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/**
29 * Adapter that exposes data from a {@link android.database.Cursor Cursor} to a
30 * {@link android.widget.ListView ListView} widget. The Cursor must include
31 * a column named "_id" or this class will not work.
32 */
33public abstract class CursorAdapter extends BaseAdapter implements Filterable,
34 CursorFilter.CursorFilterClient {
35 /**
36 * This field should be made private, so it is hidden from the SDK.
37 * {@hide}
38 */
39 protected boolean mDataValid;
40 /**
41 * This field should be made private, so it is hidden from the SDK.
42 * {@hide}
43 */
44 protected boolean mAutoRequery;
45 /**
46 * This field should be made private, so it is hidden from the SDK.
47 * {@hide}
48 */
49 protected Cursor mCursor;
50 /**
51 * This field should be made private, so it is hidden from the SDK.
52 * {@hide}
53 */
54 protected Context mContext;
55 /**
56 * This field should be made private, so it is hidden from the SDK.
57 * {@hide}
58 */
59 protected int mRowIDColumn;
60 /**
61 * This field should be made private, so it is hidden from the SDK.
62 * {@hide}
63 */
64 protected ChangeObserver mChangeObserver;
65 /**
66 * This field should be made private, so it is hidden from the SDK.
67 * {@hide}
68 */
Dianne Hackbornc9189352010-12-15 14:57:25 -080069 protected DataSetObserver mDataSetObserver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070 /**
71 * This field should be made private, so it is hidden from the SDK.
72 * {@hide}
73 */
74 protected CursorFilter mCursorFilter;
75 /**
76 * This field should be made private, so it is hidden from the SDK.
77 * {@hide}
78 */
79 protected FilterQueryProvider mFilterQueryProvider;
80
81 /**
Jeff Hamilton451513b2010-03-31 23:54:11 -050082 * If set the adapter will call requery() on the cursor whenever a content change
Dianne Hackbornc9189352010-12-15 14:57:25 -080083 * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
84 *
85 * @deprecated This option is discouraged, as it results in Cursor queries
86 * being performed on the application's UI thread and thus can cause poor
87 * responsiveness or even Application Not Responding errors. As an alternative,
88 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
Jeff Hamilton451513b2010-03-31 23:54:11 -050089 */
Dianne Hackbornc9189352010-12-15 14:57:25 -080090 @Deprecated
Jeff Hamilton451513b2010-03-31 23:54:11 -050091 public static final int FLAG_AUTO_REQUERY = 0x01;
92
93 /**
94 * If set the adapter will register a content observer on the cursor and will call
Dianne Hackbornc9189352010-12-15 14:57:25 -080095 * {@link #onContentChanged()} when a notification comes in. Be careful when
96 * using this flag: you will need to unset the current Cursor from the adapter
97 * to avoid leaks due to its registered observers. This flag is not needed
98 * when using a CursorAdapter with a
99 * {@link android.content.CursorLoader}.
Jeff Hamilton451513b2010-03-31 23:54:11 -0500100 */
101 public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
102
103 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800104 * Constructor that always enables auto-requery.
105 *
106 * @deprecated This option is discouraged, as it results in Cursor queries
107 * being performed on the application's UI thread and thus can cause poor
108 * responsiveness or even Application Not Responding errors. As an alternative,
109 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 *
111 * @param c The cursor from which to get the data.
112 * @param context The context
113 */
Dianne Hackbornc9189352010-12-15 14:57:25 -0800114 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115 public CursorAdapter(Context context, Cursor c) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500116 init(context, c, FLAG_AUTO_REQUERY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 }
118
119 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800120 * Constructor that allows control over auto-requery. It is recommended
121 * you not use this, but instead {@link #CursorAdapter(Context, Cursor, int)}.
122 * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
123 * will always be set.
124 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 * @param c The cursor from which to get the data.
126 * @param context The context
127 * @param autoRequery If true the adapter will call requery() on the
128 * cursor whenever it changes so the most recent
Dianne Hackbornc9189352010-12-15 14:57:25 -0800129 * data is always displayed. Using true here is discouraged.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130 */
131 public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
Jeff Hamilton69e87f92010-04-22 11:26:00 -0500132 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
Jeff Hamilton451513b2010-03-31 23:54:11 -0500133 }
134
135 /**
Dianne Hackbornc9189352010-12-15 14:57:25 -0800136 * Recommended constructor.
137 *
Jeff Hamilton451513b2010-03-31 23:54:11 -0500138 * @param c The cursor from which to get the data.
139 * @param context The context
Dianne Hackbornc9189352010-12-15 14:57:25 -0800140 * @param flags Flags used to determine the behavior of the adapter; may
141 * be any combination of {@link #FLAG_AUTO_REQUERY} and
142 * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
Jeff Hamilton451513b2010-03-31 23:54:11 -0500143 */
144 public CursorAdapter(Context context, Cursor c, int flags) {
145 init(context, c, flags);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 }
147
Dianne Hackbornc9189352010-12-15 14:57:25 -0800148 /**
149 * @deprecated Don't use this, use the normal constructor. This will
150 * be removed in the future.
151 */
152 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 protected void init(Context context, Cursor c, boolean autoRequery) {
Jeff Hamilton69e87f92010-04-22 11:26:00 -0500154 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
Jeff Hamilton451513b2010-03-31 23:54:11 -0500155 }
156
Dianne Hackbornc9189352010-12-15 14:57:25 -0800157 void init(Context context, Cursor c, int flags) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500158 if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
159 flags |= FLAG_REGISTER_CONTENT_OBSERVER;
160 mAutoRequery = true;
161 } else {
162 mAutoRequery = false;
163 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 boolean cursorPresent = c != null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 mCursor = c;
166 mDataValid = cursorPresent;
167 mContext = context;
168 mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
Jeff Hamilton451513b2010-03-31 23:54:11 -0500169 if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
170 mChangeObserver = new ChangeObserver();
Dianne Hackbornc9189352010-12-15 14:57:25 -0800171 mDataSetObserver = new MyDataSetObserver();
Jeff Hamilton451513b2010-03-31 23:54:11 -0500172 } else {
173 mChangeObserver = null;
Dianne Hackbornc9189352010-12-15 14:57:25 -0800174 mDataSetObserver = null;
Jeff Hamilton451513b2010-03-31 23:54:11 -0500175 }
176
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 if (cursorPresent) {
Jeff Hamilton451513b2010-03-31 23:54:11 -0500178 if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
Dianne Hackbornc9189352010-12-15 14:57:25 -0800179 if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 }
181 }
182
183 /**
184 * Returns the cursor.
185 * @return the cursor.
186 */
187 public Cursor getCursor() {
188 return mCursor;
189 }
190
191 /**
192 * @see android.widget.ListAdapter#getCount()
193 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700194 public int getCount() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800195 if (mDataValid && mCursor != null) {
196 return mCursor.getCount();
197 } else {
198 return 0;
199 }
200 }
201
202 /**
203 * @see android.widget.ListAdapter#getItem(int)
204 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700205 public Object getItem(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 if (mDataValid && mCursor != null) {
207 mCursor.moveToPosition(position);
208 return mCursor;
209 } else {
210 return null;
211 }
212 }
213
214 /**
215 * @see android.widget.ListAdapter#getItemId(int)
216 */
Jeff Hamilton2c6b3b02009-03-24 19:32:26 -0700217 public long getItemId(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 if (mDataValid && mCursor != null) {
219 if (mCursor.moveToPosition(position)) {
220 return mCursor.getLong(mRowIDColumn);
221 } else {
222 return 0;
223 }
224 } else {
225 return 0;
226 }
227 }
228
229 @Override
230 public boolean hasStableIds() {
231 return true;
232 }
233
234 /**
235 * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
236 */
237 public View getView(int position, View convertView, ViewGroup parent) {
238 if (!mDataValid) {
239 throw new IllegalStateException("this should only be called when the cursor is valid");
240 }
241 if (!mCursor.moveToPosition(position)) {
242 throw new IllegalStateException("couldn't move cursor to position " + position);
243 }
244 View v;
245 if (convertView == null) {
246 v = newView(mContext, mCursor, parent);
247 } else {
248 v = convertView;
249 }
250 bindView(v, mContext, mCursor);
251 return v;
252 }
253
254 @Override
255 public View getDropDownView(int position, View convertView, ViewGroup parent) {
256 if (mDataValid) {
257 mCursor.moveToPosition(position);
258 View v;
259 if (convertView == null) {
260 v = newDropDownView(mContext, mCursor, parent);
261 } else {
262 v = convertView;
263 }
264 bindView(v, mContext, mCursor);
265 return v;
266 } else {
267 return null;
268 }
269 }
270
271 /**
272 * Makes a new view to hold the data pointed to by cursor.
273 * @param context Interface to application's global information
274 * @param cursor The cursor from which to get the data. The cursor is already
275 * moved to the correct position.
276 * @param parent The parent to which the new view is attached to
277 * @return the newly created view.
278 */
279 public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
280
281 /**
282 * Makes a new drop down view to hold the data pointed to by cursor.
283 * @param context Interface to application's global information
284 * @param cursor The cursor from which to get the data. The cursor is already
285 * moved to the correct position.
286 * @param parent The parent to which the new view is attached to
287 * @return the newly created view.
288 */
289 public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
290 return newView(context, cursor, parent);
291 }
292
293 /**
294 * Bind an existing view to the data pointed to by cursor
295 * @param view Existing view, returned earlier by newView
296 * @param context Interface to application's global information
297 * @param cursor The cursor from which to get the data. The cursor is already
298 * moved to the correct position.
299 */
300 public abstract void bindView(View view, Context context, Cursor cursor);
301
302 /**
303 * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
304 * closed.
305 *
Dianne Hackbornc9189352010-12-15 14:57:25 -0800306 * @param cursor The new cursor to be used
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800307 */
308 public void changeCursor(Cursor cursor) {
Dianne Hackbornc9189352010-12-15 14:57:25 -0800309 Cursor old = swapCursor(cursor);
310 if (old != null) {
311 old.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800312 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800313 }
314
315 /**
316 * Swap in a new Cursor, returning the old Cursor. Unlike
317 * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
318 * closed.
319 *
320 * @param newCursor The new cursor to be used.
321 * @return Returns the previously set Cursor, or null if there wasa not one.
322 * If the given new Cursor is the same instance is the previously set
323 * Cursor, null is also returned.
324 */
325 public Cursor swapCursor(Cursor newCursor) {
326 if (newCursor == mCursor) {
327 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800329 Cursor oldCursor = mCursor;
330 if (oldCursor != null) {
331 if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
332 if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
333 }
334 mCursor = newCursor;
335 if (newCursor != null) {
336 if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
337 if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
338 mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800339 mDataValid = true;
340 // notify the observers about the new cursor
341 notifyDataSetChanged();
342 } else {
343 mRowIDColumn = -1;
344 mDataValid = false;
345 // notify the observers about the lack of a data set
346 notifyDataSetInvalidated();
347 }
Dianne Hackbornc9189352010-12-15 14:57:25 -0800348 return oldCursor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800349 }
350
351 /**
352 * <p>Converts the cursor into a CharSequence. Subclasses should override this
353 * method to convert their results. The default implementation returns an
354 * empty String for null values or the default String representation of
355 * the value.</p>
356 *
357 * @param cursor the cursor to convert to a CharSequence
358 * @return a CharSequence representing the value
359 */
360 public CharSequence convertToString(Cursor cursor) {
361 return cursor == null ? "" : cursor.toString();
362 }
363
364 /**
365 * Runs a query with the specified constraint. This query is requested
366 * by the filter attached to this adapter.
367 *
368 * The query is provided by a
369 * {@link android.widget.FilterQueryProvider}.
370 * If no provider is specified, the current cursor is not filtered and returned.
371 *
372 * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
373 * and the previous cursor is closed.
374 *
375 * This method is always executed on a background thread, not on the
376 * application's main thread (or UI thread.)
377 *
378 * Contract: when constraint is null or empty, the original results,
379 * prior to any filtering, must be returned.
380 *
381 * @param constraint the constraint with which the query must be filtered
382 *
383 * @return a Cursor representing the results of the new query
384 *
385 * @see #getFilter()
386 * @see #getFilterQueryProvider()
387 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
388 */
389 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
390 if (mFilterQueryProvider != null) {
391 return mFilterQueryProvider.runQuery(constraint);
392 }
393
394 return mCursor;
395 }
396
397 public Filter getFilter() {
398 if (mCursorFilter == null) {
399 mCursorFilter = new CursorFilter(this);
400 }
401 return mCursorFilter;
402 }
403
404 /**
405 * Returns the query filter provider used for filtering. When the
406 * provider is null, no filtering occurs.
407 *
408 * @return the current filter query provider or null if it does not exist
409 *
410 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
411 * @see #runQueryOnBackgroundThread(CharSequence)
412 */
413 public FilterQueryProvider getFilterQueryProvider() {
414 return mFilterQueryProvider;
415 }
416
417 /**
418 * Sets the query filter provider used to filter the current Cursor.
419 * The provider's
420 * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
421 * method is invoked when filtering is requested by a client of
422 * this adapter.
423 *
424 * @param filterQueryProvider the filter query provider or null to remove it
425 *
426 * @see #getFilterQueryProvider()
427 * @see #runQueryOnBackgroundThread(CharSequence)
428 */
429 public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
430 mFilterQueryProvider = filterQueryProvider;
431 }
432
433 /**
434 * Called when the {@link ContentObserver} on the cursor receives a change notification.
435 * The default implementation provides the auto-requery logic, but may be overridden by
436 * sub classes.
437 *
438 * @see ContentObserver#onChange(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800439 */
440 protected void onContentChanged() {
441 if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
Joe Onorato43a17652011-04-06 19:22:23 -0700442 if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800443 mDataValid = mCursor.requery();
444 }
445 }
446
447 private class ChangeObserver extends ContentObserver {
448 public ChangeObserver() {
449 super(new Handler());
450 }
451
452 @Override
453 public boolean deliverSelfNotifications() {
454 return true;
455 }
456
457 @Override
458 public void onChange(boolean selfChange) {
459 onContentChanged();
460 }
461 }
462
463 private class MyDataSetObserver extends DataSetObserver {
464 @Override
465 public void onChanged() {
466 mDataValid = true;
467 notifyDataSetChanged();
468 }
469
470 @Override
471 public void onInvalidated() {
472 mDataValid = false;
473 notifyDataSetInvalidated();
474 }
475 }
476
477}