Home | History | Annotate | Download | only in widget
      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 
     17 package android.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 
     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  */
     37 public 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