1/*
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;
24import 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.
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.
36 */
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     */
73    protected DataSetObserver mDataSetObserver;
74    /**
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    /**
86     * If set the adapter will call requery() on the cursor whenever a content change
87     * 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}.
93     */
94    @Deprecated
95    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
99     * {@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}.
104     */
105    public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
106
107    /**
108     * 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}.
114     *
115     * @param c The cursor from which to get the data.
116     * @param context The context
117     */
118    @Deprecated
119    public CursorAdapter(Context context, Cursor c) {
120        init(context, c, FLAG_AUTO_REQUERY);
121    }
122
123    /**
124     * 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     *
129     * @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
133     *                    data is always displayed.  Using true here is discouraged.
134     */
135    public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
136        init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
137    }
138
139    /**
140     * Recommended constructor.
141     *
142     * @param c The cursor from which to get the data.
143     * @param context The context
144     * @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}.
147     */
148    public CursorAdapter(Context context, Cursor c, int flags) {
149        init(context, c, flags);
150    }
151
152    /**
153     * @deprecated Don't use this, use the normal constructor.  This will
154     * be removed in the future.
155     */
156    @Deprecated
157    protected void init(Context context, Cursor c, boolean autoRequery) {
158        init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
159    }
160
161    void init(Context context, Cursor c, int flags) {
162        if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
163            flags |= FLAG_REGISTER_CONTENT_OBSERVER;
164            mAutoRequery = true;
165        } else {
166            mAutoRequery = false;
167        }
168        boolean cursorPresent = c != null;
169        mCursor = c;
170        mDataValid = cursorPresent;
171        mContext = context;
172        mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
173        if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
174            mChangeObserver = new ChangeObserver();
175            mDataSetObserver = new MyDataSetObserver();
176        } else {
177            mChangeObserver = null;
178            mDataSetObserver = null;
179        }
180
181        if (cursorPresent) {
182            if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
183            if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
184        }
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     */
198    public int getCount() {
199        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     */
209    public Object getItem(int position) {
210        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     */
221    public long getItemId(int position) {
222        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     *
310     * @param cursor The new cursor to be used
311     */
312    public void changeCursor(Cursor cursor) {
313        Cursor old = swapCursor(cursor);
314        if (old != null) {
315            old.close();
316        }
317    }
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;
332        }
333        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");
343            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        }
352        return oldCursor;
353    }
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)
443     */
444    protected void onContentChanged() {
445        if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
446            if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
447            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}
482