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
17package android.support.v4.widget;
18
19import android.content.Context;
20import android.database.Cursor;
21import android.net.Uri;
22import android.view.View;
23import android.widget.ImageView;
24import 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 */
33public 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
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     */
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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
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     */
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         */
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         */
395        CharSequence convertToString(Cursor cursor);
396    }
397
398}
399