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