CursorAdapter.java revision cba2e2c881e8e16ea5025b564c94320174d65f01
1/*
2 * Copyright (C) 2011 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.support.v4.widget;
18
19import android.content.Context;
20import android.database.ContentObserver;
21import android.database.Cursor;
22import android.database.DataSetObserver;
23import android.os.Handler;
24import android.util.Config;
25import android.util.Log;
26import android.view.View;
27import android.view.ViewGroup;
28import android.widget.BaseAdapter;
29import android.widget.Filter;
30import android.widget.FilterQueryProvider;
31import android.widget.Filterable;
32
33/**
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.
39 */
40public abstract class CursorAdapter extends BaseAdapter implements Filterable,
41        CursorFilter.CursorFilterClient {
42    /**
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    /**
63     * This field should be made private, so it is hidden from the SDK.
64     * {@hide}
65     */
66    protected int mRowIDColumn;
67    /**
68     * This field should be made private, so it is hidden from the SDK.
69     * {@hide}
70     */
71    protected ChangeObserver mChangeObserver;
72    /**
73     * This field should be made private, so it is hidden from the SDK.
74     * {@hide}
75     */
76    protected DataSetObserver mDataSetObserver;
77    /**
78     * This field should be made private, so it is hidden from the SDK.
79     * {@hide}
80     */
81    protected CursorFilter mCursorFilter;
82    /**
83     * This field should be made private, so it is hidden from the SDK.
84     * {@hide}
85     */
86    protected FilterQueryProvider mFilterQueryProvider;
87
88    /**
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}.
91     *
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}.
96     */
97    @Deprecated
98    public static final int FLAG_AUTO_REQUERY = 0x01;
99
100    /**
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}.
107     */
108    public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
109
110    /**
111     * Constructor that always enables auto-requery.
112     *
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}.
117     *
118     * @param c The cursor from which to get the data.
119     * @param context The context
120     */
121    @Deprecated
122    public CursorAdapter(Context context, Cursor c) {
123        init(context, c, FLAG_AUTO_REQUERY);
124    }
125
126    /**
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.
131     *
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.
137     */
138    public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
139        init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
140    }
141
142    /**
143     * Recommended constructor.
144     *
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}.
150     */
151    public CursorAdapter(Context context, Cursor c, int flags) {
152        init(context, c, flags);
153    }
154
155    /**
156     * @deprecated Don't use this, use the normal constructor.  This will
157     * be removed in the future.
158     */
159    @Deprecated
160    protected void init(Context context, Cursor c, boolean autoRequery) {
161        init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
162    }
163
164    void init(Context context, Cursor c, int flags) {
165        if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
166            flags |= FLAG_REGISTER_CONTENT_OBSERVER;
167            mAutoRequery = true;
168        } else {
169            mAutoRequery = false;
170        }
171        boolean cursorPresent = c != null;
172        mCursor = c;
173        mDataValid = cursorPresent;
174        mContext = context;
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();
179        } else {
180            mChangeObserver = null;
181            mDataSetObserver = null;
182        }
183
184        if (cursorPresent) {
185            if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
186            if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
187        }
188    }
189
190    /**
191     * Returns the cursor.
192     * @return the cursor.
193     */
194    public Cursor getCursor() {
195        return mCursor;
196    }
197
198    /**
199     * @see android.widget.ListAdapter#getCount()
200     */
201    public int getCount() {
202        if (mDataValid && mCursor != null) {
203            return mCursor.getCount();
204        } else {
205            return 0;
206        }
207    }
208
209    /**
210     * @see android.widget.ListAdapter#getItem(int)
211     */
212    public Object getItem(int position) {
213        if (mDataValid && mCursor != null) {
214            mCursor.moveToPosition(position);
215            return mCursor;
216        } else {
217            return null;
218        }
219    }
220
221    /**
222     * @see android.widget.ListAdapter#getItemId(int)
223     */
224    public long getItemId(int position) {
225        if (mDataValid && mCursor != null) {
226            if (mCursor.moveToPosition(position)) {
227                return mCursor.getLong(mRowIDColumn);
228            } else {
229                return 0;
230            }
231        } else {
232            return 0;
233        }
234    }
235
236    @Override
237    public boolean hasStableIds() {
238        return true;
239    }
240
241    /**
242     * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
243     */
244    public View getView(int position, View convertView, ViewGroup parent) {
245        if (!mDataValid) {
246            throw new IllegalStateException("this should only be called when the cursor is valid");
247        }
248        if (!mCursor.moveToPosition(position)) {
249            throw new IllegalStateException("couldn't move cursor to position " + position);
250        }
251        View v;
252        if (convertView == null) {
253            v = newView(mContext, mCursor, parent);
254        } else {
255            v = convertView;
256        }
257        bindView(v, mContext, mCursor);
258        return v;
259    }
260
261    @Override
262    public View getDropDownView(int position, View convertView, ViewGroup parent) {
263        if (mDataValid) {
264            mCursor.moveToPosition(position);
265            View v;
266            if (convertView == null) {
267                v = newDropDownView(mContext, mCursor, parent);
268            } else {
269                v = convertView;
270            }
271            bindView(v, mContext, mCursor);
272            return v;
273        } else {
274            return null;
275        }
276    }
277
278    /**
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.
285     */
286    public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
287
288    /**
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.
295     */
296    public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
297        return newView(context, cursor, parent);
298    }
299
300    /**
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.
306     */
307    public abstract void bindView(View view, Context context, Cursor cursor);
308
309    /**
310     * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
311     * closed.
312     *
313     * @param cursor The new cursor to be used
314     */
315    public void changeCursor(Cursor cursor) {
316        Cursor old = swapCursor(cursor);
317        if (old != null) {
318            old.close();
319        }
320    }
321
322    /**
323     * Swap in a new Cursor, returning the old Cursor.  Unlike
324     * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
325     * closed.
326     *
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.
331     */
332    public Cursor swapCursor(Cursor newCursor) {
333        if (newCursor == mCursor) {
334            return null;
335        }
336        Cursor oldCursor = mCursor;
337        if (oldCursor != null) {
338            if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
339            if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
340        }
341        mCursor = newCursor;
342        if (newCursor != null) {
343            if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
344            if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
345            mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
346            mDataValid = true;
347            // notify the observers about the new cursor
348            notifyDataSetChanged();
349        } else {
350            mRowIDColumn = -1;
351            mDataValid = false;
352            // notify the observers about the lack of a data set
353            notifyDataSetInvalidated();
354        }
355        return oldCursor;
356    }
357
358    /**
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
362     * the value.</p>
363     *
364     * @param cursor the cursor to convert to a CharSequence
365     * @return a CharSequence representing the value
366     */
367    public CharSequence convertToString(Cursor cursor) {
368        return cursor == null ? "" : cursor.toString();
369    }
370
371    /**
372     * Runs a query with the specified constraint. This query is requested
373     * by the filter attached to this adapter.
374     *
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.
378     *
379     * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
380     * and the previous cursor is closed.
381     *
382     * This method is always executed on a background thread, not on the
383     * application's main thread (or UI thread.)
384     *
385     * Contract: when constraint is null or empty, the original results,
386     * prior to any filtering, must be returned.
387     *
388     * @param constraint the constraint with which the query must be filtered
389     *
390     * @return a Cursor representing the results of the new query
391     *
392     * @see #getFilter()
393     * @see #getFilterQueryProvider()
394     * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
395     */
396    public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
397        if (mFilterQueryProvider != null) {
398            return mFilterQueryProvider.runQuery(constraint);
399        }
400
401        return mCursor;
402    }
403
404    public Filter getFilter() {
405        if (mCursorFilter == null) {
406            mCursorFilter = new CursorFilter(this);
407        }
408        return mCursorFilter;
409    }
410
411    /**
412     * Returns the query filter provider used for filtering. When the
413     * provider is null, no filtering occurs.
414     *
415     * @return the current filter query provider or null if it does not exist
416     *
417     * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
418     * @see #runQueryOnBackgroundThread(CharSequence)
419     */
420    public FilterQueryProvider getFilterQueryProvider() {
421        return mFilterQueryProvider;
422    }
423
424    /**
425     * Sets the query filter provider used to filter the current Cursor.
426     * The provider's
427     * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
428     * method is invoked when filtering is requested by a client of
429     * this adapter.
430     *
431     * @param filterQueryProvider the filter query provider or null to remove it
432     *
433     * @see #getFilterQueryProvider()
434     * @see #runQueryOnBackgroundThread(CharSequence)
435     */
436    public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
437        mFilterQueryProvider = filterQueryProvider;
438    }
439
440    /**
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
443     * sub classes.
444     *
445     * @see ContentObserver#onChange(boolean)
446     */
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();
451        }
452    }
453
454    private class ChangeObserver extends ContentObserver {
455        public ChangeObserver() {
456            super(new Handler());
457        }
458
459        @Override
460        public boolean deliverSelfNotifications() {
461            return true;
462        }
463
464        @Override
465        public void onChange(boolean selfChange) {
466            onContentChanged();
467        }
468    }
469
470    private class MyDataSetObserver extends DataSetObserver {
471        @Override
472        public void onChanged() {
473            mDataValid = true;
474            notifyDataSetChanged();
475        }
476
477        @Override
478        public void onInvalidated() {
479            mDataValid = false;
480            notifyDataSetInvalidated();
481        }
482    }
483
484}
485