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