• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.support.v4.widget;
18 
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.Log;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.widget.BaseAdapter;
28 import android.widget.Filter;
29 import android.widget.FilterQueryProvider;
30 import android.widget.Filterable;
31 
32 /**
33  * Static library support version of the framework's {@link android.widget.CursorAdapter}.
34  * Used to write apps that run on platforms prior to Android 3.0.  When running
35  * on Android 3.0 or above, this implementation is still used; it does not try
36  * to switch to the framework's implementation.  See the framework SDK
37  * documentation for a class overview.
38  */
39 public abstract class CursorAdapter extends BaseAdapter implements Filterable,
40         CursorFilter.CursorFilterClient {
41     /**
42      * This field should be made private, so it is hidden from the SDK.
43      * {@hide}
44      */
45     protected boolean mDataValid;
46     /**
47      * This field should be made private, so it is hidden from the SDK.
48      * {@hide}
49      */
50     protected boolean mAutoRequery;
51     /**
52      * This field should be made private, so it is hidden from the SDK.
53      * {@hide}
54      */
55     protected Cursor mCursor;
56     /**
57      * This field should be made private, so it is hidden from the SDK.
58      * {@hide}
59      */
60     protected Context mContext;
61     /**
62      * This field should be made private, so it is hidden from the SDK.
63      * {@hide}
64      */
65     protected int mRowIDColumn;
66     /**
67      * This field should be made private, so it is hidden from the SDK.
68      * {@hide}
69      */
70     protected ChangeObserver mChangeObserver;
71     /**
72      * This field should be made private, so it is hidden from the SDK.
73      * {@hide}
74      */
75     protected DataSetObserver mDataSetObserver;
76     /**
77      * This field should be made private, so it is hidden from the SDK.
78      * {@hide}
79      */
80     protected CursorFilter mCursorFilter;
81     /**
82      * This field should be made private, so it is hidden from the SDK.
83      * {@hide}
84      */
85     protected FilterQueryProvider mFilterQueryProvider;
86 
87     /**
88      * If set the adapter will call requery() on the cursor whenever a content change
89      * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
90      *
91      * @deprecated This option is discouraged, as it results in Cursor queries
92      * being performed on the application's UI thread and thus can cause poor
93      * responsiveness or even Application Not Responding errors.  As an alternative,
94      * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
95      */
96     @Deprecated
97     public static final int FLAG_AUTO_REQUERY = 0x01;
98 
99     /**
100      * If set the adapter will register a content observer on the cursor and will call
101      * {@link #onContentChanged()} when a notification comes in.  Be careful when
102      * using this flag: you will need to unset the current Cursor from the adapter
103      * to avoid leaks due to its registered observers.  This flag is not needed
104      * when using a CursorAdapter with a
105      * {@link android.content.CursorLoader}.
106      */
107     public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
108 
109     /**
110      * Constructor that always enables auto-requery.
111      *
112      * @deprecated This option is discouraged, as it results in Cursor queries
113      * being performed on the application's UI thread and thus can cause poor
114      * responsiveness or even Application Not Responding errors.  As an alternative,
115      * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
116      *
117      * @param c The cursor from which to get the data.
118      * @param context The context
119      */
120     @Deprecated
CursorAdapter(Context context, Cursor c)121     public CursorAdapter(Context context, Cursor c) {
122         init(context, c, FLAG_AUTO_REQUERY);
123     }
124 
125     /**
126      * Constructor that allows control over auto-requery.  It is recommended
127      * you not use this, but instead {@link #CursorAdapter(Context, Cursor, int)}.
128      * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
129      * will always be set.
130      *
131      * @param c The cursor from which to get the data.
132      * @param context The context
133      * @param autoRequery If true the adapter will call requery() on the
134      *                    cursor whenever it changes so the most recent
135      *                    data is always displayed.  Using true here is discouraged.
136      */
CursorAdapter(Context context, Cursor c, boolean autoRequery)137     public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
138         init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
139     }
140 
141     /**
142      * Recommended constructor.
143      *
144      * @param c The cursor from which to get the data.
145      * @param context The context
146      * @param flags Flags used to determine the behavior of the adapter; may
147      * be any combination of {@link #FLAG_AUTO_REQUERY} and
148      * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
149      */
CursorAdapter(Context context, Cursor c, int flags)150     public CursorAdapter(Context context, Cursor c, int flags) {
151         init(context, c, flags);
152     }
153 
154     /**
155      * @deprecated Don't use this, use the normal constructor.  This will
156      * be removed in the future.
157      */
158     @Deprecated
init(Context context, Cursor c, boolean autoRequery)159     protected void init(Context context, Cursor c, boolean autoRequery) {
160         init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
161     }
162 
init(Context context, Cursor c, int flags)163     void init(Context context, Cursor c, int flags) {
164         if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
165             flags |= FLAG_REGISTER_CONTENT_OBSERVER;
166             mAutoRequery = true;
167         } else {
168             mAutoRequery = false;
169         }
170         boolean cursorPresent = c != null;
171         mCursor = c;
172         mDataValid = cursorPresent;
173         mContext = context;
174         mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
175         if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
176             mChangeObserver = new ChangeObserver();
177             mDataSetObserver = new MyDataSetObserver();
178         } else {
179             mChangeObserver = null;
180             mDataSetObserver = null;
181         }
182 
183         if (cursorPresent) {
184             if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
185             if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
186         }
187     }
188 
189     /**
190      * Returns the cursor.
191      * @return the cursor.
192      */
getCursor()193     public Cursor getCursor() {
194         return mCursor;
195     }
196 
197     /**
198      * @see android.widget.ListAdapter#getCount()
199      */
getCount()200     public int getCount() {
201         if (mDataValid && mCursor != null) {
202             return mCursor.getCount();
203         } else {
204             return 0;
205         }
206     }
207 
208     /**
209      * @see android.widget.ListAdapter#getItem(int)
210      */
getItem(int position)211     public Object getItem(int position) {
212         if (mDataValid && mCursor != null) {
213             mCursor.moveToPosition(position);
214             return mCursor;
215         } else {
216             return null;
217         }
218     }
219 
220     /**
221      * @see android.widget.ListAdapter#getItemId(int)
222      */
getItemId(int position)223     public long getItemId(int position) {
224         if (mDataValid && mCursor != null) {
225             if (mCursor.moveToPosition(position)) {
226                 return mCursor.getLong(mRowIDColumn);
227             } else {
228                 return 0;
229             }
230         } else {
231             return 0;
232         }
233     }
234 
235     @Override
hasStableIds()236     public boolean hasStableIds() {
237         return true;
238     }
239 
240     /**
241      * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
242      */
getView(int position, View convertView, ViewGroup parent)243     public View getView(int position, View convertView, ViewGroup parent) {
244         if (!mDataValid) {
245             throw new IllegalStateException("this should only be called when the cursor is valid");
246         }
247         if (!mCursor.moveToPosition(position)) {
248             throw new IllegalStateException("couldn't move cursor to position " + position);
249         }
250         View v;
251         if (convertView == null) {
252             v = newView(mContext, mCursor, parent);
253         } else {
254             v = convertView;
255         }
256         bindView(v, mContext, mCursor);
257         return v;
258     }
259 
260     @Override
getDropDownView(int position, View convertView, ViewGroup parent)261     public View getDropDownView(int position, View convertView, ViewGroup parent) {
262         if (mDataValid) {
263             mCursor.moveToPosition(position);
264             View v;
265             if (convertView == null) {
266                 v = newDropDownView(mContext, mCursor, parent);
267             } else {
268                 v = convertView;
269             }
270             bindView(v, mContext, mCursor);
271             return v;
272         } else {
273             return null;
274         }
275     }
276 
277     /**
278      * Makes a new view to hold the data pointed to by cursor.
279      * @param context Interface to application's global information
280      * @param cursor The cursor from which to get the data. The cursor is already
281      * moved to the correct position.
282      * @param parent The parent to which the new view is attached to
283      * @return the newly created view.
284      */
newView(Context context, Cursor cursor, ViewGroup parent)285     public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
286 
287     /**
288      * Makes a new drop down view to hold the data pointed to by cursor.
289      * @param context Interface to application's global information
290      * @param cursor The cursor from which to get the data. The cursor is already
291      * moved to the correct position.
292      * @param parent The parent to which the new view is attached to
293      * @return the newly created view.
294      */
newDropDownView(Context context, Cursor cursor, ViewGroup parent)295     public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
296         return newView(context, cursor, parent);
297     }
298 
299     /**
300      * Bind an existing view to the data pointed to by cursor
301      * @param view Existing view, returned earlier by newView
302      * @param context Interface to application's global information
303      * @param cursor The cursor from which to get the data. The cursor is already
304      * moved to the correct position.
305      */
bindView(View view, Context context, Cursor cursor)306     public abstract void bindView(View view, Context context, Cursor cursor);
307 
308     /**
309      * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
310      * closed.
311      *
312      * @param cursor The new cursor to be used
313      */
changeCursor(Cursor cursor)314     public void changeCursor(Cursor cursor) {
315         Cursor old = swapCursor(cursor);
316         if (old != null) {
317             old.close();
318         }
319     }
320 
321     /**
322      * Swap in a new Cursor, returning the old Cursor.  Unlike
323      * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
324      * closed.
325      *
326      * @param newCursor The new cursor to be used.
327      * @return Returns the previously set Cursor, or null if there wasa not one.
328      * If the given new Cursor is the same instance is the previously set
329      * Cursor, null is also returned.
330      */
swapCursor(Cursor newCursor)331     public Cursor swapCursor(Cursor newCursor) {
332         if (newCursor == mCursor) {
333             return null;
334         }
335         Cursor oldCursor = mCursor;
336         if (oldCursor != null) {
337             if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
338             if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
339         }
340         mCursor = newCursor;
341         if (newCursor != null) {
342             if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
343             if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
344             mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
345             mDataValid = true;
346             // notify the observers about the new cursor
347             notifyDataSetChanged();
348         } else {
349             mRowIDColumn = -1;
350             mDataValid = false;
351             // notify the observers about the lack of a data set
352             notifyDataSetInvalidated();
353         }
354         return oldCursor;
355     }
356 
357     /**
358      * <p>Converts the cursor into a CharSequence. Subclasses should override this
359      * method to convert their results. The default implementation returns an
360      * empty String for null values or the default String representation of
361      * the value.</p>
362      *
363      * @param cursor the cursor to convert to a CharSequence
364      * @return a CharSequence representing the value
365      */
convertToString(Cursor cursor)366     public CharSequence convertToString(Cursor cursor) {
367         return cursor == null ? "" : cursor.toString();
368     }
369 
370     /**
371      * Runs a query with the specified constraint. This query is requested
372      * by the filter attached to this adapter.
373      *
374      * The query is provided by a
375      * {@link android.widget.FilterQueryProvider}.
376      * If no provider is specified, the current cursor is not filtered and returned.
377      *
378      * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
379      * and the previous cursor is closed.
380      *
381      * This method is always executed on a background thread, not on the
382      * application's main thread (or UI thread.)
383      *
384      * Contract: when constraint is null or empty, the original results,
385      * prior to any filtering, must be returned.
386      *
387      * @param constraint the constraint with which the query must be filtered
388      *
389      * @return a Cursor representing the results of the new query
390      *
391      * @see #getFilter()
392      * @see #getFilterQueryProvider()
393      * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
394      */
runQueryOnBackgroundThread(CharSequence constraint)395     public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
396         if (mFilterQueryProvider != null) {
397             return mFilterQueryProvider.runQuery(constraint);
398         }
399 
400         return mCursor;
401     }
402 
getFilter()403     public Filter getFilter() {
404         if (mCursorFilter == null) {
405             mCursorFilter = new CursorFilter(this);
406         }
407         return mCursorFilter;
408     }
409 
410     /**
411      * Returns the query filter provider used for filtering. When the
412      * provider is null, no filtering occurs.
413      *
414      * @return the current filter query provider or null if it does not exist
415      *
416      * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
417      * @see #runQueryOnBackgroundThread(CharSequence)
418      */
getFilterQueryProvider()419     public FilterQueryProvider getFilterQueryProvider() {
420         return mFilterQueryProvider;
421     }
422 
423     /**
424      * Sets the query filter provider used to filter the current Cursor.
425      * The provider's
426      * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
427      * method is invoked when filtering is requested by a client of
428      * this adapter.
429      *
430      * @param filterQueryProvider the filter query provider or null to remove it
431      *
432      * @see #getFilterQueryProvider()
433      * @see #runQueryOnBackgroundThread(CharSequence)
434      */
setFilterQueryProvider(FilterQueryProvider filterQueryProvider)435     public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
436         mFilterQueryProvider = filterQueryProvider;
437     }
438 
439     /**
440      * Called when the {@link ContentObserver} on the cursor receives a change notification.
441      * The default implementation provides the auto-requery logic, but may be overridden by
442      * sub classes.
443      *
444      * @see ContentObserver#onChange(boolean)
445      */
onContentChanged()446     protected void onContentChanged() {
447         if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
448             if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
449             mDataValid = mCursor.requery();
450         }
451     }
452 
453     private class ChangeObserver extends ContentObserver {
ChangeObserver()454         public ChangeObserver() {
455             super(new Handler());
456         }
457 
458         @Override
deliverSelfNotifications()459         public boolean deliverSelfNotifications() {
460             return true;
461         }
462 
463         @Override
onChange(boolean selfChange)464         public void onChange(boolean selfChange) {
465             onContentChanged();
466         }
467     }
468 
469     private class MyDataSetObserver extends DataSetObserver {
470         @Override
onChanged()471         public void onChanged() {
472             mDataValid = true;
473             notifyDataSetChanged();
474         }
475 
476         @Override
onInvalidated()477         public void onInvalidated() {
478             mDataValid = false;
479             notifyDataSetInvalidated();
480         }
481     }
482 
483 }
484