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