• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import android.content.Context;
20 import android.database.Cursor;
21 import android.net.Uri;
22 import android.view.View;
23 import android.view.ViewGroup;
24 import android.widget.ImageView;
25 import android.widget.SimpleCursorAdapter;
26 import android.widget.SimpleCursorAdapter.CursorToStringConverter;
27 import android.widget.SimpleCursorAdapter.ViewBinder;
28 import android.widget.TextView;
29 
30 import com.xtremelabs.robolectric.internal.Implementation;
31 import com.xtremelabs.robolectric.internal.Implements;
32 import com.xtremelabs.robolectric.internal.RealObject;
33 
34 /**
35  * An easy adapter to map columns from a cursor to TextViews or ImageViews
36  * defined in an XML file. You can specify which columns you want, which
37  * views you want to display the columns, and the XML file that defines
38  * the appearance of these views.
39  *
40  * Binding occurs in two phases. First, if a
41  * {@link com.xtremelabs.robolectric.shadows.ShadowSimpleCursorAdapter.ViewBinder} is available,
42  * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
43  * is invoked. If the returned value is true, binding has occured. If the
44  * returned value is false and the view to bind is a TextView,
45  * {@link #setViewText(TextView, String)} is invoked. If the returned value
46  * is false and the view to bind is an ImageView,
47  * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
48  * binding can be found, an {@link IllegalStateException} is thrown.
49  *
50  * If this adapter is used with filtering, for instance in an
51  * {@link android.widget.AutoCompleteTextView}, you can use the
52  * {@link com.xtremelabs.robolectric.shadows.ShadowSimpleCursorAdapter.CursorToStringConverter} and the
53  * {@link android.widget.FilterQueryProvider} interfaces
54  * to get control over the filtering process. You can refer to
55  * {@link #convertToString(android.database.Cursor)} and
56  * {@link #runQueryOnBackgroundThread(CharSequence)} for more information.
57  */
58 @Implements(SimpleCursorAdapter.class)
59 public class ShadowSimpleCursorAdapter extends ShadowResourceCursorAdapter {
60 	@RealObject private SimpleCursorAdapter realSimpleCursorAdapter;
61 
62     /**
63      * A list of columns containing the data to bind to the UI.
64      * This field should be made private, so it is hidden from the SDK.
65      * {@hide}
66      */
67     protected int[] mFrom;
68     /**
69      * A list of View ids representing the views to which the data must be bound.
70      * This field should be made private, so it is hidden from the SDK.
71      * {@hide}
72      */
73     protected int[] mTo;
74 
75     private int mStringConversionColumn = -1;
76     private CursorToStringConverter mCursorToStringConverter;
77     private ViewBinder mViewBinder;
78     private String[] mOriginalFrom;
79 
80     /**
81      * Constructor.
82      *
83      * @param context The context where the ListView associated with this
84      *            SimpleListItemFactory is running
85      * @param layout resource identifier of a layout file that defines the views
86      *            for this list item. The layout file should include at least
87      *            those named views defined in "to"
88      * @param c The database cursor.  Can be null if the cursor is not available yet.
89      * @param from A list of column names representing the data to bind to the UI.  Can be null
90      *            if the cursor is not available yet.
91      * @param to The views that should display column in the "from" parameter.
92      *            These should all be TextViews. The first N views in this list
93      *            are given the values of the first N columns in the from
94      *            parameter.  Can be null if the cursor is not available yet.
95      */
__constructor__(Context context, int layout, Cursor c, String[] from, int[] to)96     public void __constructor__(Context context, int layout, Cursor c, String[] from, int[] to) {
97     	super.__constructor__(context, layout, c);
98         mTo = to;
99         mOriginalFrom = from;
100         findColumns(from);
101     }
102 
103     /**
104      * Binds all of the field names passed into the "to" parameter of the
105      * constructor with their corresponding cursor columns as specified in the
106      * "from" parameter.
107      *
108      * Binding occurs in two phases. First, if a
109      * {@link com.xtremelabs.robolectric.shadows.ShadowSimpleCursorAdapter.ViewBinder} is available,
110      * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
111      * is invoked. If the returned value is true, binding has occured. If the
112      * returned value is false and the view to bind is a TextView,
113      * {@link #setViewText(TextView, String)} is invoked. If the returned value is
114      * false and the view to bind is an ImageView,
115      * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
116      * binding can be found, an {@link IllegalStateException} is thrown.
117      *
118      * @throws IllegalStateException if binding cannot occur
119      *
120      * @see android.widget.CursorAdapter#bindView(android.view.View,
121      *      android.content.Context, android.database.Cursor)
122      * @see #getViewBinder()
123      * @see #setViewBinder(com.xtremelabs.robolectric.shadows.ShadowSimpleCursorAdapter.ViewBinder)
124      * @see #setViewImage(ImageView, String)
125      * @see #setViewText(TextView, String)
126      */
127     @Implementation
bindView(View view, Context context, Cursor cursor)128     public void bindView(View view, Context context, Cursor cursor) {
129         final ViewBinder binder = mViewBinder;
130         final int count = mTo.length;
131         final int[] from = mFrom;
132         final int[] to = mTo;
133 
134         for (int i = 0; i < count; i++) {
135             final View v = view.findViewById(to[i]);
136             if (v != null) {
137                 boolean bound = false;
138                 if (binder != null) {
139                     bound = binder.setViewValue(v, cursor, from[i]);
140                 }
141 
142                 if (!bound) {
143                     String text = cursor.getString(from[i]);
144                     if (text == null) {
145                         text = "";
146                     }
147 
148                     if (v instanceof TextView) {
149                         setViewText((TextView) v, text);
150                     } else if (v instanceof ImageView) {
151                         setViewImage((ImageView) v, text);
152                     } else {
153                         throw new IllegalStateException(v.getClass().getName() + " is not a " +
154                                 " view that can be bounds by this SimpleCursorAdapter");
155                     }
156                 }
157             }
158         }
159     }
160 
161     /**
162      * Returns the {@link ViewBinder} used to bind data to views.
163      *
164      * @return a ViewBinder or null if the binder does not exist
165      *
166      * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
167      * @see #setViewBinder(com.xtremelabs.robolectric.shadows.ShadowSimpleCursorAdapter.ViewBinder)
168      */
169     @Implementation
getViewBinder()170     public ViewBinder getViewBinder() {
171         return mViewBinder;
172     }
173 
174     /**
175      * Sets the binder used to bind data to views.
176      *
177      * @param viewBinder the binder used to bind data to views, can be null to
178      *        remove the existing binder
179      *
180      * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
181      * @see #getViewBinder()
182      */
183     @Implementation
setViewBinder(ViewBinder viewBinder)184     public void setViewBinder(ViewBinder viewBinder) {
185         mViewBinder = viewBinder;
186     }
187 
188     /**
189      * Called by bindView() to set the image for an ImageView but only if
190      * there is no existing ViewBinder or if the existing ViewBinder cannot
191      * handle binding to an ImageView.
192      *
193      * By default, the value will be treated as an image resource. If the
194      * value cannot be used as an image resource, the value is used as an
195      * image Uri.
196      *
197      * Intended to be overridden by Adapters that need to filter strings
198      * retrieved from the database.
199      *
200      * @param v ImageView to receive an image
201      * @param value the value retrieved from the cursor
202      */
203     @Implementation
setViewImage(ImageView v, String value)204     public void setViewImage(ImageView v, String value) {
205         try {
206             v.setImageResource(Integer.parseInt(value));
207         } catch (NumberFormatException nfe) {
208             v.setImageURI(Uri.parse(value));
209         }
210     }
211 
212     /**
213      * Called by bindView() to set the text for a TextView but only if
214      * there is no existing ViewBinder or if the existing ViewBinder cannot
215      * handle binding to an TextView.
216      *
217      * Intended to be overridden by Adapters that need to filter strings
218      * retrieved from the database.
219      *
220      * @param v TextView to receive text
221      * @param text the text to be set for the TextView
222      */
223     @Implementation
setViewText(TextView v, String text)224     public void setViewText(TextView v, String text) {
225         v.setText(text);
226     }
227 
228     /**
229      * Return the index of the column used to get a String representation
230      * of the Cursor.
231      *
232      * @return a valid index in the current Cursor or -1
233      *
234      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
235      * @see #setStringConversionColumn(int)
236      * @see #setCursorToStringConverter(com.xtremelabs.robolectric.shadows.ShadowSimpleCursorAdapter.CursorToStringConverter)
237      * @see #getCursorToStringConverter()
238      */
239     @Implementation
getStringConversionColumn()240     public int getStringConversionColumn() {
241         return mStringConversionColumn;
242     }
243 
244     /**
245      * Defines the index of the column in the Cursor used to get a String
246      * representation of that Cursor. The column is used to convert the
247      * Cursor to a String only when the current CursorToStringConverter
248      * is null.
249      *
250      * @param stringConversionColumn a valid index in the current Cursor or -1 to use the default
251      *        conversion mechanism
252      *
253      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
254      * @see #getStringConversionColumn()
255      * @see #setCursorToStringConverter(com.xtremelabs.robolectric.shadows.ShadowSimpleCursorAdapter.CursorToStringConverter)
256      * @see #getCursorToStringConverter()
257      */
258     @Implementation
setStringConversionColumn(int stringConversionColumn)259     public void setStringConversionColumn(int stringConversionColumn) {
260         mStringConversionColumn = stringConversionColumn;
261     }
262 
263     /**
264      * Returns the converter used to convert the filtering Cursor
265      * into a String.
266      *
267      * @return null if the converter does not exist or an instance of
268      *         {@link com.xtremelabs.robolectric.shadows.ShadowSimpleCursorAdapter.CursorToStringConverter}
269      *
270      * @see #setCursorToStringConverter(com.xtremelabs.robolectric.shadows.ShadowSimpleCursorAdapter.CursorToStringConverter)
271      * @see #getStringConversionColumn()
272      * @see #setStringConversionColumn(int)
273      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
274      */
275     @Implementation
getCursorToStringConverter()276     public CursorToStringConverter getCursorToStringConverter() {
277         return mCursorToStringConverter;
278     }
279 
280     /**
281      * Sets the converter  used to convert the filtering Cursor
282      * into a String.
283      *
284      * @param cursorToStringConverter the Cursor to String converter, or
285      *        null to remove the converter
286      *
287      * @see #setCursorToStringConverter(com.xtremelabs.robolectric.shadows.ShadowSimpleCursorAdapter.CursorToStringConverter)
288      * @see #getStringConversionColumn()
289      * @see #setStringConversionColumn(int)
290      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
291      */
292     @Implementation
setCursorToStringConverter(CursorToStringConverter cursorToStringConverter)293     public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) {
294         mCursorToStringConverter = cursorToStringConverter;
295     }
296 
297     /**
298      * Returns a CharSequence representation of the specified Cursor as defined
299      * by the current CursorToStringConverter. If no CursorToStringConverter
300      * has been set, the String conversion column is used instead. If the
301      * conversion column is -1, the returned String is empty if the cursor
302      * is null or Cursor.toString().
303      *
304      * @param cursor the Cursor to convert to a CharSequence
305      *
306      * @return a non-null CharSequence representing the cursor
307      */
308     @Implementation
convertToString(Cursor cursor)309     public CharSequence convertToString(Cursor cursor) {
310         if (mCursorToStringConverter != null) {
311             return mCursorToStringConverter.convertToString(cursor);
312         } else if (mStringConversionColumn > -1) {
313             return cursor.getString(mStringConversionColumn);
314         }
315 
316         return realSimpleCursorAdapter.convertToString(cursor);
317     }
318 
319     /**
320      * Create a map from an array of strings to an array of column-id integers in mCursor.
321      * If mCursor is null, the array will be discarded.
322      *
323      * @param from the Strings naming the columns of interest
324      */
findColumns(String[] from)325     private void findColumns(String[] from) {
326         if (mCursor != null) {
327             int i;
328             int count = from.length;
329             if (mFrom == null || mFrom.length != count) {
330                 mFrom = new int[count];
331             }
332             for (i = 0; i < count; i++) {
333                 mFrom[i] = mCursor.getColumnIndexOrThrow(from[i]);
334             }
335         } else {
336             mFrom = null;
337         }
338     }
339 
340     @Implementation
changeCursor(Cursor c)341     public void changeCursor(Cursor c) {
342         realSimpleCursorAdapter.changeCursor(c);
343         // rescan columns in case cursor layout is different
344         findColumns(mOriginalFrom);
345     }
346 
347     /**
348      * Change the cursor and change the column-to-view mappings at the same time.
349      *
350      * @param c The database cursor.  Can be null if the cursor is not available yet.
351      * @param from A list of column names representing the data to bind to the UI.  Can be null
352      *            if the cursor is not available yet.
353      * @param to The views that should display column in the "from" parameter.
354      *            These should all be TextViews. The first N views in this list
355      *            are given the values of the first N columns in the from
356      *            parameter.  Can be null if the cursor is not available yet.
357      */
358     @Implementation
changeCursorAndColumns(Cursor c, String[] from, int[] to)359     public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
360         mOriginalFrom = from;
361         mTo = to;
362         realSimpleCursorAdapter.changeCursor(c);
363         findColumns(mOriginalFrom);
364     }
365 
366     ///////////////////////////////////////////////////////////////////////////////////////////////
367     // Implementation from CursorAdapter
368 
369     /**
370      * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
371      */
372     @Implementation
getView(int position, View convertView, ViewGroup parent)373     public View getView(int position, View convertView, ViewGroup parent) {
374         if (!mDataValid) {
375             throw new IllegalStateException("this should only be called when the cursor is valid");
376         }
377         if (!mCursor.moveToPosition(position)) {
378             throw new IllegalStateException("couldn't move cursor to position " + position);
379         }
380         View v;
381         if (convertView == null) {
382             v = newView(mContext, mCursor, parent);
383         } else {
384             v = convertView;
385         }
386         bindView(v, mContext, mCursor);
387         return v;
388     }
389 
390     @Implementation
getDropDownView(int position, View convertView, ViewGroup parent)391     public View getDropDownView(int position, View convertView, ViewGroup parent) {
392         if (mDataValid) {
393             mCursor.moveToPosition(position);
394             View v;
395             if (convertView == null) {
396                 v = newDropDownView(mContext, mCursor, parent);
397             } else {
398                 v = convertView;
399             }
400             bindView(v, mContext, mCursor);
401             return v;
402         } else {
403             return null;
404         }
405     }
406 
407 }