• 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      */
193     @Override
getCursor()194     public Cursor getCursor() {
195         return mCursor;
196     }
197 
198     /**
199      * @see android.widget.ListAdapter#getCount()
200      */
201     @Override
getCount()202     public int getCount() {
203         if (mDataValid && mCursor != null) {
204             return mCursor.getCount();
205         } else {
206             return 0;
207         }
208     }
209 
210     /**
211      * @see android.widget.ListAdapter#getItem(int)
212      */
213     @Override
getItem(int position)214     public Object getItem(int position) {
215         if (mDataValid && mCursor != null) {
216             mCursor.moveToPosition(position);
217             return mCursor;
218         } else {
219             return null;
220         }
221     }
222 
223     /**
224      * @see android.widget.ListAdapter#getItemId(int)
225      */
226     @Override
getItemId(int position)227     public long getItemId(int position) {
228         if (mDataValid && mCursor != null) {
229             if (mCursor.moveToPosition(position)) {
230                 return mCursor.getLong(mRowIDColumn);
231             } else {
232                 return 0;
233             }
234         } else {
235             return 0;
236         }
237     }
238 
239     @Override
hasStableIds()240     public boolean hasStableIds() {
241         return true;
242     }
243 
244     /**
245      * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
246      */
247     @Override
getView(int position, View convertView, ViewGroup parent)248     public View getView(int position, View convertView, ViewGroup parent) {
249         if (!mDataValid) {
250             throw new IllegalStateException("this should only be called when the cursor is valid");
251         }
252         if (!mCursor.moveToPosition(position)) {
253             throw new IllegalStateException("couldn't move cursor to position " + position);
254         }
255         View v;
256         if (convertView == null) {
257             v = newView(mContext, mCursor, parent);
258         } else {
259             v = convertView;
260         }
261         bindView(v, mContext, mCursor);
262         return v;
263     }
264 
265     @Override
getDropDownView(int position, View convertView, ViewGroup parent)266     public View getDropDownView(int position, View convertView, ViewGroup parent) {
267         if (mDataValid) {
268             mCursor.moveToPosition(position);
269             View v;
270             if (convertView == null) {
271                 v = newDropDownView(mContext, mCursor, parent);
272             } else {
273                 v = convertView;
274             }
275             bindView(v, mContext, mCursor);
276             return v;
277         } else {
278             return null;
279         }
280     }
281 
282     /**
283      * Makes a new view to hold the data pointed to by cursor.
284      * @param context Interface to application's global information
285      * @param cursor The cursor from which to get the data. The cursor is already
286      * moved to the correct position.
287      * @param parent The parent to which the new view is attached to
288      * @return the newly created view.
289      */
newView(Context context, Cursor cursor, ViewGroup parent)290     public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
291 
292     /**
293      * Makes a new drop down view to hold the data pointed to by cursor.
294      * @param context Interface to application's global information
295      * @param cursor The cursor from which to get the data. The cursor is already
296      * moved to the correct position.
297      * @param parent The parent to which the new view is attached to
298      * @return the newly created view.
299      */
newDropDownView(Context context, Cursor cursor, ViewGroup parent)300     public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
301         return newView(context, cursor, parent);
302     }
303 
304     /**
305      * Bind an existing view to the data pointed to by cursor
306      * @param view Existing view, returned earlier by newView
307      * @param context Interface to application's global information
308      * @param cursor The cursor from which to get the data. The cursor is already
309      * moved to the correct position.
310      */
bindView(View view, Context context, Cursor cursor)311     public abstract void bindView(View view, Context context, Cursor cursor);
312 
313     /**
314      * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
315      * closed.
316      *
317      * @param cursor The new cursor to be used
318      */
319     @Override
changeCursor(Cursor cursor)320     public void changeCursor(Cursor cursor) {
321         Cursor old = swapCursor(cursor);
322         if (old != null) {
323             old.close();
324         }
325     }
326 
327     /**
328      * Swap in a new Cursor, returning the old Cursor.  Unlike
329      * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
330      * closed.
331      *
332      * @param newCursor The new cursor to be used.
333      * @return Returns the previously set Cursor, or null if there was not one.
334      * If the given new Cursor is the same instance is the previously set
335      * Cursor, null is also returned.
336      */
swapCursor(Cursor newCursor)337     public Cursor swapCursor(Cursor newCursor) {
338         if (newCursor == mCursor) {
339             return null;
340         }
341         Cursor oldCursor = mCursor;
342         if (oldCursor != null) {
343             if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
344             if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
345         }
346         mCursor = newCursor;
347         if (newCursor != null) {
348             if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
349             if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
350             mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
351             mDataValid = true;
352             // notify the observers about the new cursor
353             notifyDataSetChanged();
354         } else {
355             mRowIDColumn = -1;
356             mDataValid = false;
357             // notify the observers about the lack of a data set
358             notifyDataSetInvalidated();
359         }
360         return oldCursor;
361     }
362 
363     /**
364      * <p>Converts the cursor into a CharSequence. Subclasses should override this
365      * method to convert their results. The default implementation returns an
366      * empty String for null values or the default String representation of
367      * the value.</p>
368      *
369      * @param cursor the cursor to convert to a CharSequence
370      * @return a CharSequence representing the value
371      */
372     @Override
convertToString(Cursor cursor)373     public CharSequence convertToString(Cursor cursor) {
374         return cursor == null ? "" : cursor.toString();
375     }
376 
377     /**
378      * Runs a query with the specified constraint. This query is requested
379      * by the filter attached to this adapter.
380      *
381      * The query is provided by a
382      * {@link android.widget.FilterQueryProvider}.
383      * If no provider is specified, the current cursor is not filtered and returned.
384      *
385      * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
386      * and the previous cursor is closed.
387      *
388      * This method is always executed on a background thread, not on the
389      * application's main thread (or UI thread.)
390      *
391      * Contract: when constraint is null or empty, the original results,
392      * prior to any filtering, must be returned.
393      *
394      * @param constraint the constraint with which the query must be filtered
395      *
396      * @return a Cursor representing the results of the new query
397      *
398      * @see #getFilter()
399      * @see #getFilterQueryProvider()
400      * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
401      */
402     @Override
runQueryOnBackgroundThread(CharSequence constraint)403     public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
404         if (mFilterQueryProvider != null) {
405             return mFilterQueryProvider.runQuery(constraint);
406         }
407 
408         return mCursor;
409     }
410 
411     @Override
getFilter()412     public Filter getFilter() {
413         if (mCursorFilter == null) {
414             mCursorFilter = new CursorFilter(this);
415         }
416         return mCursorFilter;
417     }
418 
419     /**
420      * Returns the query filter provider used for filtering. When the
421      * provider is null, no filtering occurs.
422      *
423      * @return the current filter query provider or null if it does not exist
424      *
425      * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
426      * @see #runQueryOnBackgroundThread(CharSequence)
427      */
getFilterQueryProvider()428     public FilterQueryProvider getFilterQueryProvider() {
429         return mFilterQueryProvider;
430     }
431 
432     /**
433      * Sets the query filter provider used to filter the current Cursor.
434      * The provider's
435      * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
436      * method is invoked when filtering is requested by a client of
437      * this adapter.
438      *
439      * @param filterQueryProvider the filter query provider or null to remove it
440      *
441      * @see #getFilterQueryProvider()
442      * @see #runQueryOnBackgroundThread(CharSequence)
443      */
setFilterQueryProvider(FilterQueryProvider filterQueryProvider)444     public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
445         mFilterQueryProvider = filterQueryProvider;
446     }
447 
448     /**
449      * Called when the {@link ContentObserver} on the cursor receives a change notification.
450      * The default implementation provides the auto-requery logic, but may be overridden by
451      * sub classes.
452      *
453      * @see ContentObserver#onChange(boolean)
454      */
onContentChanged()455     protected void onContentChanged() {
456         if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
457             if (false) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
458             mDataValid = mCursor.requery();
459         }
460     }
461 
462     private class ChangeObserver extends ContentObserver {
ChangeObserver()463         ChangeObserver() {
464             super(new Handler());
465         }
466 
467         @Override
deliverSelfNotifications()468         public boolean deliverSelfNotifications() {
469             return true;
470         }
471 
472         @Override
onChange(boolean selfChange)473         public void onChange(boolean selfChange) {
474             onContentChanged();
475         }
476     }
477 
478     private class MyDataSetObserver extends DataSetObserver {
479         @Override
onChanged()480         public void onChanged() {
481             mDataValid = true;
482             notifyDataSetChanged();
483         }
484 
485         @Override
onInvalidated()486         public void onInvalidated() {
487             mDataValid = false;
488             notifyDataSetInvalidated();
489         }
490     }
491 
492 }
493