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(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(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(android.view.View,
113      *      android.content.Context, android.database.Cursor)
114      * @see #getViewBinder()
115      * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
116      * @see #setViewImage(ImageView, String)
117      * @see #setViewText(TextView, String)
118      */
119     @Override
bindView(View view, Context context, Cursor cursor)120     public void bindView(View view, Context context, Cursor cursor) {
121         final ViewBinder binder = mViewBinder;
122         final int count = mTo.length;
123         final int[] from = mFrom;
124         final int[] to = mTo;
125 
126         for (int i = 0; i < count; i++) {
127             final View v = view.findViewById(to[i]);
128             if (v != null) {
129                 boolean bound = false;
130                 if (binder != null) {
131                     bound = binder.setViewValue(v, cursor, from[i]);
132                 }
133 
134                 if (!bound) {
135                     String text = cursor.getString(from[i]);
136                     if (text == null) {
137                         text = "";
138                     }
139 
140                     if (v instanceof TextView) {
141                         setViewText((TextView) v, text);
142                     } else if (v instanceof ImageView) {
143                         setViewImage((ImageView) v, text);
144                     } else {
145                         throw new IllegalStateException(v.getClass().getName() + " is not a " +
146                                 " view that can be bounds by this SimpleCursorAdapter");
147                     }
148                 }
149             }
150         }
151     }
152 
153     /**
154      * Returns the {@link ViewBinder} used to bind data to views.
155      *
156      * @return a ViewBinder or null if the binder does not exist
157      *
158      * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
159      * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
160      */
getViewBinder()161     public ViewBinder getViewBinder() {
162         return mViewBinder;
163     }
164 
165     /**
166      * Sets the binder used to bind data to views.
167      *
168      * @param viewBinder the binder used to bind data to views, can be null to
169      *        remove the existing binder
170      *
171      * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
172      * @see #getViewBinder()
173      */
setViewBinder(ViewBinder viewBinder)174     public void setViewBinder(ViewBinder viewBinder) {
175         mViewBinder = viewBinder;
176     }
177 
178     /**
179      * Called by bindView() to set the image for an ImageView but only if
180      * there is no existing ViewBinder or if the existing ViewBinder cannot
181      * handle binding to an ImageView.
182      *
183      * By default, the value will be treated as an image resource. If the
184      * value cannot be used as an image resource, the value is used as an
185      * image Uri.
186      *
187      * Intended to be overridden by Adapters that need to filter strings
188      * retrieved from the database.
189      *
190      * @param v ImageView to receive an image
191      * @param value the value retrieved from the cursor
192      */
setViewImage(ImageView v, String value)193     public void setViewImage(ImageView v, String value) {
194         try {
195             v.setImageResource(Integer.parseInt(value));
196         } catch (NumberFormatException nfe) {
197             v.setImageURI(Uri.parse(value));
198         }
199     }
200 
201     /**
202      * Called by bindView() to set the text for a TextView but only if
203      * there is no existing ViewBinder or if the existing ViewBinder cannot
204      * handle binding to an TextView.
205      *
206      * Intended to be overridden by Adapters that need to filter strings
207      * retrieved from the database.
208      *
209      * @param v TextView to receive text
210      * @param text the text to be set for the TextView
211      */
setViewText(TextView v, String text)212     public void setViewText(TextView v, String text) {
213         v.setText(text);
214     }
215 
216     /**
217      * Return the index of the column used to get a String representation
218      * of the Cursor.
219      *
220      * @return a valid index in the current Cursor or -1
221      *
222      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
223      * @see #setStringConversionColumn(int)
224      * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
225      * @see #getCursorToStringConverter()
226      */
getStringConversionColumn()227     public int getStringConversionColumn() {
228         return mStringConversionColumn;
229     }
230 
231     /**
232      * Defines the index of the column in the Cursor used to get a String
233      * representation of that Cursor. The column is used to convert the
234      * Cursor to a String only when the current CursorToStringConverter
235      * is null.
236      *
237      * @param stringConversionColumn a valid index in the current Cursor or -1 to use the default
238      *        conversion mechanism
239      *
240      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
241      * @see #getStringConversionColumn()
242      * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
243      * @see #getCursorToStringConverter()
244      */
setStringConversionColumn(int stringConversionColumn)245     public void setStringConversionColumn(int stringConversionColumn) {
246         mStringConversionColumn = stringConversionColumn;
247     }
248 
249     /**
250      * Returns the converter used to convert the filtering Cursor
251      * into a String.
252      *
253      * @return null if the converter does not exist or an instance of
254      *         {@link android.widget.SimpleCursorAdapter.CursorToStringConverter}
255      *
256      * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
257      * @see #getStringConversionColumn()
258      * @see #setStringConversionColumn(int)
259      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
260      */
getCursorToStringConverter()261     public CursorToStringConverter getCursorToStringConverter() {
262         return mCursorToStringConverter;
263     }
264 
265     /**
266      * Sets the converter  used to convert the filtering Cursor
267      * into a String.
268      *
269      * @param cursorToStringConverter the Cursor to String converter, or
270      *        null to remove the converter
271      *
272      * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
273      * @see #getStringConversionColumn()
274      * @see #setStringConversionColumn(int)
275      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
276      */
setCursorToStringConverter(CursorToStringConverter cursorToStringConverter)277     public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) {
278         mCursorToStringConverter = cursorToStringConverter;
279     }
280 
281     /**
282      * Returns a CharSequence representation of the specified Cursor as defined
283      * by the current CursorToStringConverter. If no CursorToStringConverter
284      * has been set, the String conversion column is used instead. If the
285      * conversion column is -1, the returned String is empty if the cursor
286      * is null or Cursor.toString().
287      *
288      * @param cursor the Cursor to convert to a CharSequence
289      *
290      * @return a non-null CharSequence representing the cursor
291      */
292     @Override
convertToString(Cursor cursor)293     public CharSequence convertToString(Cursor cursor) {
294         if (mCursorToStringConverter != null) {
295             return mCursorToStringConverter.convertToString(cursor);
296         } else if (mStringConversionColumn > -1) {
297             return cursor.getString(mStringConversionColumn);
298         }
299 
300         return super.convertToString(cursor);
301     }
302 
303     /**
304      * Create a map from an array of strings to an array of column-id integers in mCursor.
305      * If mCursor is null, the array will be discarded.
306      *
307      * @param from the Strings naming the columns of interest
308      */
findColumns(String[] from)309     private void findColumns(String[] from) {
310         if (mCursor != 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] = mCursor.getColumnIndexOrThrow(from[i]);
318             }
319         } else {
320             mFrom = null;
321         }
322     }
323 
324     @Override
swapCursor(Cursor c)325     public Cursor swapCursor(Cursor c) {
326         Cursor res = super.swapCursor(c);
327         // rescan columns in case cursor layout is different
328         findColumns(mOriginalFrom);
329         return res;
330     }
331 
332     /**
333      * Change the cursor and change the column-to-view mappings at the same time.
334      *
335      * @param c The database cursor.  Can be null if the cursor is not available yet.
336      * @param from A list of column names representing the data to bind to the UI.  Can be null
337      *            if the cursor is not available yet.
338      * @param to The views that should display column in the "from" parameter.
339      *            These should all be TextViews. The first N views in this list
340      *            are given the values of the first N columns in the from
341      *            parameter.  Can be null if the cursor is not available yet.
342      */
changeCursorAndColumns(Cursor c, String[] from, int[] to)343     public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
344         mOriginalFrom = from;
345         mTo = to;
346         super.changeCursor(c);
347         findColumns(mOriginalFrom);
348     }
349 
350     /**
351      * This class can be used by external clients of SimpleCursorAdapter
352      * to bind values fom the Cursor to views.
353      *
354      * You should use this class to bind values from the Cursor to views
355      * that are not directly supported by SimpleCursorAdapter or to
356      * change the way binding occurs for views supported by
357      * SimpleCursorAdapter.
358      *
359      * @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
360      * @see SimpleCursorAdapter#setViewImage(ImageView, String)
361      * @see SimpleCursorAdapter#setViewText(TextView, String)
362      */
363     public static interface ViewBinder {
364         /**
365          * Binds the Cursor column defined by the specified index to the specified view.
366          *
367          * When binding is handled by this ViewBinder, this method must return true.
368          * If this method returns false, SimpleCursorAdapter will attempts to handle
369          * the binding on its own.
370          *
371          * @param view the view to bind the data to
372          * @param cursor the cursor to get the data from
373          * @param columnIndex the column at which the data can be found in the cursor
374          *
375          * @return true if the data was bound to the view, false otherwise
376          */
setViewValue(View view, Cursor cursor, int columnIndex)377         boolean setViewValue(View view, Cursor cursor, int columnIndex);
378     }
379 
380     /**
381      * This class can be used by external clients of SimpleCursorAdapter
382      * to define how the Cursor should be converted to a String.
383      *
384      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
385      */
386     public static interface CursorToStringConverter {
387         /**
388          * Returns a CharSequence representing the specified Cursor.
389          *
390          * @param cursor the cursor for which a CharSequence representation
391          *        is requested
392          *
393          * @return a non-null CharSequence representing the cursor
394          */
convertToString(Cursor cursor)395         CharSequence convertToString(Cursor cursor);
396     }
397 
398 }
399