2 * Copyright (C) 2011 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.support.v2.widget;
19 import android.content.Context;
20 import android.database.ContentObserver;
21 import android.database.Cursor;
22 import android.database.DataSetObserver;
23 import android.os.Handler;
24 import android.util.Config;
25 import android.util.Log;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.widget.BaseAdapter;
29 import android.widget.Filter;
30 import android.widget.FilterQueryProvider;
31 import android.widget.Filterable;
34 * Static library support version of the framework's {@link android.widget.CursorAdapter}.
35 * Used to write apps that run on platforms prior to Android 3.0. When running
36 * on Android 3.0 or above, this implementation is still used; it does not try
37 * to switch to the framework's implementation. See the framework SDK
38 * documentation for a class overview.
40 public abstract class CursorAdapter extends BaseAdapter implements Filterable,
41 CursorFilter.CursorFilterClient {
43 * This field should be made private, so it is hidden from the SDK.
46 protected boolean mDataValid;
48 * This field should be made private, so it is hidden from the SDK.
51 protected boolean mAutoRequery;
53 * This field should be made private, so it is hidden from the SDK.
56 protected Cursor mCursor;
58 * This field should be made private, so it is hidden from the SDK.
61 protected Context mContext;
63 * This field should be made private, so it is hidden from the SDK.
66 protected int mRowIDColumn;
68 * This field should be made private, so it is hidden from the SDK.
71 protected ChangeObserver mChangeObserver;
73 * This field should be made private, so it is hidden from the SDK.
76 protected DataSetObserver mDataSetObserver;
78 * This field should be made private, so it is hidden from the SDK.
81 protected CursorFilter mCursorFilter;
83 * This field should be made private, so it is hidden from the SDK.
86 protected FilterQueryProvider mFilterQueryProvider;
89 * If set the adapter will call requery() on the cursor whenever a content change
90 * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
92 * @deprecated This option is discouraged, as it results in Cursor queries
93 * being performed on the application's UI thread and thus can cause poor
94 * responsiveness or even Application Not Responding errors. As an alternative,
95 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
98 public static final int FLAG_AUTO_REQUERY = 0x01;
101 * If set the adapter will register a content observer on the cursor and will call
102 * {@link #onContentChanged()} when a notification comes in. Be careful when
103 * using this flag: you will need to unset the current Cursor from the adapter
104 * to avoid leaks due to its registered observers. This flag is not needed
105 * when using a CursorAdapter with a
106 * {@link android.content.CursorLoader}.
108 public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
111 * Constructor that always enables auto-requery.
113 * @deprecated This option is discouraged, as it results in Cursor queries
114 * being performed on the application's UI thread and thus can cause poor
115 * responsiveness or even Application Not Responding errors. As an alternative,
116 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
118 * @param c The cursor from which to get the data.
119 * @param context The context
122 public CursorAdapter(Context context, Cursor c) {
123 init(context, c, FLAG_AUTO_REQUERY);
127 * Constructor that allows control over auto-requery. It is recommended
128 * you not use this, but instead {@link #CursorAdapter(Context, Cursor, int)}.
129 * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
130 * will always be set.
132 * @param c The cursor from which to get the data.
133 * @param context The context
134 * @param autoRequery If true the adapter will call requery() on the
135 * cursor whenever it changes so the most recent
136 * data is always displayed. Using true here is discouraged.
138 public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
139 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
143 * Recommended constructor.
145 * @param c The cursor from which to get the data.
146 * @param context The context
147 * @param flags Flags used to determine the behavior of the adapter; may
148 * be any combination of {@link #FLAG_AUTO_REQUERY} and
149 * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
151 public CursorAdapter(Context context, Cursor c, int flags) {
152 init(context, c, flags);
156 * @deprecated Don't use this, use the normal constructor. This will
157 * be removed in the future.
160 protected void init(Context context, Cursor c, boolean autoRequery) {
161 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
164 void init(Context context, Cursor c, int flags) {
165 if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
166 flags |= FLAG_REGISTER_CONTENT_OBSERVER;
169 mAutoRequery = false;
171 boolean cursorPresent = c != null;
173 mDataValid = cursorPresent;
175 mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
176 if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
177 mChangeObserver = new ChangeObserver();
178 mDataSetObserver = new MyDataSetObserver();
180 mChangeObserver = null;
181 mDataSetObserver = null;
185 if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
186 if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
191 * Returns the cursor.
192 * @return the cursor.
194 public Cursor getCursor() {
199 * @see android.widget.ListAdapter#getCount()
201 public int getCount() {
202 if (mDataValid && mCursor != null) {
203 return mCursor.getCount();
210 * @see android.widget.ListAdapter#getItem(int)
212 public Object getItem(int position) {
213 if (mDataValid && mCursor != null) {
214 mCursor.moveToPosition(position);
222 * @see android.widget.ListAdapter#getItemId(int)
224 public long getItemId(int position) {
225 if (mDataValid && mCursor != null) {
226 if (mCursor.moveToPosition(position)) {
227 return mCursor.getLong(mRowIDColumn);
237 public boolean hasStableIds() {
242 * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
244 public View getView(int position, View convertView, ViewGroup parent) {
246 throw new IllegalStateException("this should only be called when the cursor is valid");
248 if (!mCursor.moveToPosition(position)) {
249 throw new IllegalStateException("couldn't move cursor to position " + position);
252 if (convertView == null) {
253 v = newView(mContext, mCursor, parent);
257 bindView(v, mContext, mCursor);
262 public View getDropDownView(int position, View convertView, ViewGroup parent) {
264 mCursor.moveToPosition(position);
266 if (convertView == null) {
267 v = newDropDownView(mContext, mCursor, parent);
271 bindView(v, mContext, mCursor);
279 * Makes a new view to hold the data pointed to by cursor.
280 * @param context Interface to application's global information
281 * @param cursor The cursor from which to get the data. The cursor is already
282 * moved to the correct position.
283 * @param parent The parent to which the new view is attached to
284 * @return the newly created view.
286 public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
289 * Makes a new drop down view to hold the data pointed to by cursor.
290 * @param context Interface to application's global information
291 * @param cursor The cursor from which to get the data. The cursor is already
292 * moved to the correct position.
293 * @param parent The parent to which the new view is attached to
294 * @return the newly created view.
296 public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
297 return newView(context, cursor, parent);
301 * Bind an existing view to the data pointed to by cursor
302 * @param view Existing view, returned earlier by newView
303 * @param context Interface to application's global information
304 * @param cursor The cursor from which to get the data. The cursor is already
305 * moved to the correct position.
307 public abstract void bindView(View view, Context context, Cursor cursor);
310 * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
313 * @param cursor The new cursor to be used
315 public void changeCursor(Cursor cursor) {
316 Cursor old = swapCursor(cursor);
323 * Swap in a new Cursor, returning the old Cursor. Unlike
324 * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
327 * @param newCursor The new cursor to be used.
328 * @return Returns the previously set Cursor, or null if there wasa not one.
329 * If the given new Cursor is the same instance is the previously set
330 * Cursor, null is also returned.
332 public Cursor swapCursor(Cursor newCursor) {
333 if (newCursor == mCursor) {
336 Cursor oldCursor = mCursor;
337 if (oldCursor != null) {
338 if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
339 if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
342 if (newCursor != null) {
343 if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
344 if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
345 mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
347 // notify the observers about the new cursor
348 notifyDataSetChanged();
352 // notify the observers about the lack of a data set
353 notifyDataSetInvalidated();
359 * <p>Converts the cursor into a CharSequence. Subclasses should override this
360 * method to convert their results. The default implementation returns an
361 * empty String for null values or the default String representation of
364 * @param cursor the cursor to convert to a CharSequence
365 * @return a CharSequence representing the value
367 public CharSequence convertToString(Cursor cursor) {
368 return cursor == null ? "" : cursor.toString();
372 * Runs a query with the specified constraint. This query is requested
373 * by the filter attached to this adapter.
375 * The query is provided by a
376 * {@link android.widget.FilterQueryProvider}.
377 * If no provider is specified, the current cursor is not filtered and returned.
379 * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
380 * and the previous cursor is closed.
382 * This method is always executed on a background thread, not on the
383 * application's main thread (or UI thread.)
385 * Contract: when constraint is null or empty, the original results,
386 * prior to any filtering, must be returned.
388 * @param constraint the constraint with which the query must be filtered
390 * @return a Cursor representing the results of the new query
393 * @see #getFilterQueryProvider()
394 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
396 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
397 if (mFilterQueryProvider != null) {
398 return mFilterQueryProvider.runQuery(constraint);
404 public Filter getFilter() {
405 if (mCursorFilter == null) {
406 mCursorFilter = new CursorFilter(this);
408 return mCursorFilter;
412 * Returns the query filter provider used for filtering. When the
413 * provider is null, no filtering occurs.
415 * @return the current filter query provider or null if it does not exist
417 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
418 * @see #runQueryOnBackgroundThread(CharSequence)
420 public FilterQueryProvider getFilterQueryProvider() {
421 return mFilterQueryProvider;
425 * Sets the query filter provider used to filter the current Cursor.
427 * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
428 * method is invoked when filtering is requested by a client of
431 * @param filterQueryProvider the filter query provider or null to remove it
433 * @see #getFilterQueryProvider()
434 * @see #runQueryOnBackgroundThread(CharSequence)
436 public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
437 mFilterQueryProvider = filterQueryProvider;
441 * Called when the {@link ContentObserver} on the cursor receives a change notification.
442 * The default implementation provides the auto-requery logic, but may be overridden by
445 * @see ContentObserver#onChange(boolean)
447 protected void onContentChanged() {
448 if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
449 if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
450 mDataValid = mCursor.requery();
454 private class ChangeObserver extends ContentObserver {
455 public ChangeObserver() {
456 super(new Handler());
460 public boolean deliverSelfNotifications() {
465 public void onChange(boolean selfChange) {
470 private class MyDataSetObserver extends DataSetObserver {
472 public void onChanged() {
474 notifyDataSetChanged();
478 public void onInvalidated() {
480 notifyDataSetInvalidated();