Home | History | Annotate | Download | only in shadows
      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 com.xtremelabs.robolectric.shadows;
     18 
     19 
     20 import android.content.Context;
     21 import android.database.ContentObserver;
     22 import android.database.Cursor;
     23 import android.database.DataSetObserver;
     24 import android.os.Handler;
     25 import android.util.Config;
     26 import android.util.Log;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 import android.widget.CursorAdapter;
     30 import android.widget.FilterQueryProvider;
     31 
     32 import com.xtremelabs.robolectric.internal.Implementation;
     33 import com.xtremelabs.robolectric.internal.Implements;
     34 
     35 import java.util.ArrayList;
     36 import java.util.List;
     37 
     38 /**
     39  * Adapter that exposes data from a {@link android.database.Cursor Cursor} to a
     40  * {@link android.widget.ListView ListView} widget. The Cursor must include
     41  * a column named "_id" or this class will not work.
     42  */
     43 @Implements(CursorAdapter.class)
     44 public class ShadowCursorAdapter extends ShadowBaseAdapter {
     45 
     46     private List<View> views = new ArrayList<View>();
     47 
     48     @Implementation
     49     public View getView(int position, View convertView, ViewGroup parent) {
     50     	// if the cursor is null OR there are no views to dispense return null
     51         if (this.mCursor == null || views.size() == 0 ) {
     52             return null;
     53         }
     54 
     55         if (convertView != null) {
     56             return convertView;
     57         }
     58 
     59         return views.get(position);
     60     }
     61 
     62     /**
     63      * Non-Android API.  Set a list of views to be returned for successive
     64      * calls to getView().
     65      *
     66      * @param views
     67      */
     68     public void setViews(List<View> views) {
     69         this.views = views;
     70     }
     71 
     72     /**
     73      * This field should be made private, so it is hidden from the SDK.
     74      * {@hide}
     75      */
     76     protected boolean mDataValid;
     77     /**
     78      * This field should be made private, so it is hidden from the SDK.
     79      * {@hide}
     80      */
     81     protected boolean mAutoRequery;
     82     /**
     83      * This field should be made private, so it is hidden from the SDK.
     84      * {@hide}
     85      */
     86     protected Cursor mCursor;
     87     /**
     88      * This field should be made private, so it is hidden from the SDK.
     89      * {@hide}
     90      */
     91     protected Context mContext;
     92     /**
     93      * This field should be made private, so it is hidden from the SDK.
     94      * {@hide}
     95      */
     96     protected int mRowIDColumn;
     97     /**
     98      * This field should be made private, so it is hidden from the SDK.
     99      * {@hide}
    100      */
    101     protected ChangeObserver mChangeObserver;
    102     /**
    103      * This field should be made private, so it is hidden from the SDK.
    104      * {@hide}
    105      */
    106     protected DataSetObserver mDataSetObserver = new MyDataSetObserver();
    107 //    /**
    108 //     * This field should be made private, so it is hidden from the SDK.
    109 //     * {@hide}
    110 //     */
    111 //    protected CursorFilter__FromAndroid mCursorFilter;
    112     /**
    113      * This field should be made private, so it is hidden from the SDK.
    114      * {@hide}
    115      */
    116     protected FilterQueryProvider mFilterQueryProvider;
    117 
    118     /**
    119      * Constructor. The adapter will call requery() on the cursor whenever
    120      * it changes so that the most recent data is always displayed.
    121      *
    122      * @param c       The cursor from which to get the data.
    123      * @param context The context
    124      */
    125     public void __constructor__(Context context, Cursor c) {
    126         initialize(context, c, true);
    127     }
    128 
    129     /**
    130      * Constructor
    131      *
    132      * @param c           The cursor from which to get the data.
    133      * @param context     The context
    134      * @param autoRequery If true the adapter will call requery() on the
    135      *                    cursor whenever it changes so the most recent
    136      *                    data is always displayed.
    137      */
    138     public void __constructor__(Context context, Cursor c, boolean autoRequery) {
    139         initialize(context, c, autoRequery);
    140     }
    141 
    142     // renamed from Android source so as not to conflict with RobolectricWiringTest
    143     private void initialize(Context context, Cursor c, boolean autoRequery) {
    144         boolean cursorPresent = c != null;
    145         mAutoRequery = autoRequery;
    146         mCursor = c;
    147         mDataValid = cursorPresent;
    148         mContext = context;
    149         mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
    150         mChangeObserver = new ChangeObserver();
    151         if (cursorPresent) {
    152             c.registerContentObserver(mChangeObserver);
    153             c.registerDataSetObserver(mDataSetObserver);
    154         }
    155     }
    156 
    157     /**
    158      * Returns the cursor.
    159      *
    160      * @return the cursor.
    161      */
    162     @Implementation
    163     public Cursor getCursor() {
    164         return mCursor;
    165     }
    166 
    167     /**
    168      * @see android.widget.ListAdapter#getCount()
    169      */
    170     @Implementation
    171     public int getCount() {
    172         if (mDataValid && mCursor != null) {
    173             return mCursor.getCount();
    174         } else {
    175             return 0;
    176         }
    177     }
    178 
    179     /**
    180      * @see android.widget.ListAdapter#getItem(int)
    181      */
    182     @Implementation
    183     public Object getItem(int position) {
    184         if (mDataValid && mCursor != null) {
    185             mCursor.moveToPosition(position);
    186             return mCursor;
    187         } else {
    188             return null;
    189         }
    190     }
    191 
    192     /**
    193      * @see android.widget.ListAdapter#getItemId(int)
    194      */
    195     @Implementation
    196     public long getItemId(int position) {
    197         if (mDataValid && mCursor != null) {
    198             this.mCursor.getColumnIndexOrThrow("_id");
    199             if (mCursor.moveToPosition(position)) {
    200                 return mCursor.getLong(mRowIDColumn);
    201             } else {
    202                 return 0;
    203             }
    204         } else {
    205             return 0;
    206         }
    207     }
    208 
    209     @Implementation
    210     public boolean hasStableIds() {
    211         return true;
    212     }
    213 
    214 //    /**
    215 //     * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
    216 //     */
    217 //    @Implementation
    218 //    public View getView(int position, View convertView, ViewGroup parent) {
    219 //        if (!mDataValid) {
    220 //            throw new IllegalStateException("this should only be called when the cursor is valid");
    221 //        }
    222 //        if (!mCursor.moveToPosition(position)) {
    223 //            throw new IllegalStateException("couldn't move cursor to position " + position);
    224 //        }
    225 //        View v;
    226 //        if (convertView == null) {
    227 //            v = newView(mContext, mCursor, parent);
    228 //        } else {
    229 //            v = convertView;
    230 //        }
    231 //        bindView(v, mContext, mCursor);
    232 //        return v;
    233 //    }
    234 //
    235 //    @Implementation
    236 //    public View getDropDownView(int position, View convertView, ViewGroup parent) {
    237 //        if (mDataValid) {
    238 //            mCursor.moveToPosition(position);
    239 //            View v;
    240 //            if (convertView == null) {
    241 //                v = newDropDownView(mContext, mCursor, parent);
    242 //            } else {
    243 //                v = convertView;
    244 //            }
    245 //            bindView(v, mContext, mCursor);
    246 //            return v;
    247 //        } else {
    248 //            return null;
    249 //        }
    250 //    }
    251 
    252 //    /**
    253 //     * Makes a new view to hold the data pointed to by cursor.
    254 //     * @param context Interface to application's global information
    255 //     * @param cursor The cursor from which to get the data. The cursor is already
    256 //     * moved to the correct position.
    257 //     * @param parent The parent to which the new view is attached to
    258 //     * @return the newly created view.
    259 //     */
    260 //    public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
    261 
    262 //    /**
    263 //     * Makes a new drop down view to hold the data pointed to by cursor.
    264 //     * @param context Interface to application's global information
    265 //     * @param cursor The cursor from which to get the data. The cursor is already
    266 //     * moved to the correct position.
    267 //     * @param parent The parent to which the new view is attached to
    268 //     * @return the newly created view.
    269 //     */
    270 //    @Implementation
    271 //    public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
    272 //        return newView(context, cursor, parent);
    273 //    }
    274 
    275 //    /**
    276 //     * Bind an existing view to the data pointed to by cursor
    277 //     * @param view Existing view, returned earlier by newView
    278 //     * @param context Interface to application's global information
    279 //     * @param cursor The cursor from which to get the data. The cursor is already
    280 //     * moved to the correct position.
    281 //     */
    282 //    public abstract void bindView(View view, Context context, Cursor cursor);
    283 
    284     /**
    285      * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
    286      * closed.
    287      *
    288      * @param cursor the new cursor to be used
    289      */
    290     @Implementation
    291     public void changeCursor(Cursor cursor) {
    292         if (cursor == mCursor) {
    293             return;
    294         }
    295         if (mCursor != null) {
    296             mCursor.unregisterContentObserver(mChangeObserver);
    297             mCursor.unregisterDataSetObserver(mDataSetObserver);
    298             mCursor.close();
    299         }
    300         mCursor = cursor;
    301         if (cursor != null) {
    302             cursor.registerContentObserver(mChangeObserver);
    303             cursor.registerDataSetObserver(mDataSetObserver);
    304             mRowIDColumn = cursor.getColumnIndexOrThrow("_id");
    305             mDataValid = true;
    306             // notify the observers about the new cursor
    307             notifyDataSetChanged();
    308         } else {
    309             mRowIDColumn = -1;
    310             mDataValid = false;
    311             // notify the observers about the lack of a data set
    312             notifyDataSetInvalidated();
    313         }
    314     }
    315 
    316     /**
    317      * <p>Converts the cursor into a CharSequence. Subclasses should override this
    318      * method to convert their results. The default implementation returns an
    319      * empty String for null values or the default String representation of
    320      * the value.</p>
    321      *
    322      * @param cursor the cursor to convert to a CharSequence
    323      * @return a CharSequence representing the value
    324      */
    325     @Implementation
    326     public CharSequence convertToString(Cursor cursor) {
    327         return cursor == null ? "" : cursor.toString();
    328     }
    329 
    330     /**
    331      * Runs a query with the specified constraint. This query is requested
    332      * by the filter attached to this adapter.
    333      * <p/>
    334      * The query is provided by a
    335      * {@link android.widget.FilterQueryProvider}.
    336      * If no provider is specified, the current cursor is not filtered and returned.
    337      * <p/>
    338      * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
    339      * and the previous cursor is closed.
    340      * <p/>
    341      * This method is always executed on a background thread, not on the
    342      * application's main thread (or UI thread.)
    343      * <p/>
    344      * Contract: when constraint is null or empty, the original results,
    345      * prior to any filtering, must be returned.
    346      *
    347      * @param constraint the constraint with which the query must be filtered
    348      * @return a Cursor representing the results of the new query
    349      * @see #getFilter()
    350      * @see #getFilterQueryProvider()
    351      * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
    352      */
    353     @Implementation
    354     public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
    355         if (mFilterQueryProvider != null) {
    356             return mFilterQueryProvider.runQuery(constraint);
    357         }
    358 
    359         return mCursor;
    360     }
    361 
    362 //    @Implementation
    363 //    public Filter getFilter() {
    364 //        if (mCursorFilter == null) {
    365 //            mCursorFilter = new CursorFilter__FromAndroid(this);
    366 //        }
    367 //        return mCursorFilter;
    368 //    }
    369 
    370     /**
    371      * Returns the query filter provider used for filtering. When the
    372      * provider is null, no filtering occurs.
    373      *
    374      * @return the current filter query provider or null if it does not exist
    375      * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
    376      * @see #runQueryOnBackgroundThread(CharSequence)
    377      */
    378     @Implementation
    379     public FilterQueryProvider getFilterQueryProvider() {
    380         return mFilterQueryProvider;
    381     }
    382 
    383     /**
    384      * Sets the query filter provider used to filter the current Cursor.
    385      * The provider's
    386      * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
    387      * method is invoked when filtering is requested by a client of
    388      * this adapter.
    389      *
    390      * @param filterQueryProvider the filter query provider or null to remove it
    391      * @see #getFilterQueryProvider()
    392      * @see #runQueryOnBackgroundThread(CharSequence)
    393      */
    394     @Implementation
    395     public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
    396         mFilterQueryProvider = filterQueryProvider;
    397     }
    398 
    399     /**
    400      * Called when the {@link ContentObserver} on the cursor receives a change notification.
    401      * The default implementation provides the auto-requery logic, but may be overridden by
    402      * sub classes.
    403      *
    404      * @see ContentObserver#onChange(boolean)
    405      */
    406     // renamed from Android source so as not to conflict with RobolectricWiringTest
    407     protected void onContentChangedInternal() {
    408         if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
    409             if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
    410             mDataValid = mCursor.requery();
    411         }
    412     }
    413 
    414     private class ChangeObserver extends ContentObserver {
    415         public ChangeObserver() {
    416             super(new Handler());
    417         }
    418 
    419         @Override
    420         public boolean deliverSelfNotifications() {
    421             return true;
    422         }
    423 
    424         @Override
    425         public void onChange(boolean selfChange) {
    426             onContentChangedInternal();
    427         }
    428     }
    429 
    430     private class MyDataSetObserver extends DataSetObserver {
    431         @Override
    432         public void onChanged() {
    433             mDataValid = true;
    434             notifyDataSetChanged();
    435         }
    436 
    437         @Override
    438         public void onInvalidated() {
    439             mDataValid = false;
    440             notifyDataSetInvalidated();
    441         }
    442     }
    443 
    444 }