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