switch to alsa.omap3 module
[android_pandora.git] / apps / AndroidSupportV2 / src / android / support / v2 / widget / CursorAdapter.java
CommitLineData
811a5a4a 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.v2.widget;
18
19import android.content.Context;
20import android.database.ContentObserver;
21import android.database.Cursor;
22import android.database.DataSetObserver;
23import android.os.Handler;
24import android.util.Config;
25import android.util.Log;
26import android.view.View;
27import android.view.ViewGroup;
28import android.widget.BaseAdapter;
29import android.widget.Filter;
30import android.widget.FilterQueryProvider;
31import android.widget.Filterable;
32
33/**
34 * Static library support version of the framework's {@link android.widget.CursorAdapter}.
35 * Used to write apps that run on platforms prior to Android 3.0. When running
36 * on Android 3.0 or above, this implementation is still used; it does not try
37 * to switch to the framework's implementation. See the framework SDK
38 * documentation for a class overview.
39 */
40public abstract class CursorAdapter extends BaseAdapter implements Filterable,
41 CursorFilter.CursorFilterClient {
42 /**
43 * This field should be made private, so it is hidden from the SDK.
44 * {@hide}
45 */
46 protected boolean mDataValid;
47 /**
48 * This field should be made private, so it is hidden from the SDK.
49 * {@hide}
50 */
51 protected boolean mAutoRequery;
52 /**
53 * This field should be made private, so it is hidden from the SDK.
54 * {@hide}
55 */
56 protected Cursor mCursor;
57 /**
58 * This field should be made private, so it is hidden from the SDK.
59 * {@hide}
60 */
61 protected Context mContext;
62 /**
63 * This field should be made private, so it is hidden from the SDK.
64 * {@hide}
65 */
66 protected int mRowIDColumn;
67 /**
68 * This field should be made private, so it is hidden from the SDK.
69 * {@hide}
70 */
71 protected ChangeObserver mChangeObserver;
72 /**
73 * This field should be made private, so it is hidden from the SDK.
74 * {@hide}
75 */
76 protected DataSetObserver mDataSetObserver;
77 /**
78 * This field should be made private, so it is hidden from the SDK.
79 * {@hide}
80 */
81 protected CursorFilter mCursorFilter;
82 /**
83 * This field should be made private, so it is hidden from the SDK.
84 * {@hide}
85 */
86 protected FilterQueryProvider mFilterQueryProvider;
87
88 /**
89 * If set the adapter will call requery() on the cursor whenever a content change
90 * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
91 *
92 * @deprecated This option is discouraged, as it results in Cursor queries
93 * being performed on the application's UI thread and thus can cause poor
94 * responsiveness or even Application Not Responding errors. As an alternative,
95 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
96 */
97 @Deprecated
98 public static final int FLAG_AUTO_REQUERY = 0x01;
99
100 /**
101 * If set the adapter will register a content observer on the cursor and will call
102 * {@link #onContentChanged()} when a notification comes in. Be careful when
103 * using this flag: you will need to unset the current Cursor from the adapter
104 * to avoid leaks due to its registered observers. This flag is not needed
105 * when using a CursorAdapter with a
106 * {@link android.content.CursorLoader}.
107 */
108 public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
109
110 /**
111 * Constructor that always enables auto-requery.
112 *
113 * @deprecated This option is discouraged, as it results in Cursor queries
114 * being performed on the application's UI thread and thus can cause poor
115 * responsiveness or even Application Not Responding errors. As an alternative,
116 * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
117 *
118 * @param c The cursor from which to get the data.
119 * @param context The context
120 */
121 @Deprecated
122 public CursorAdapter(Context context, Cursor c) {
123 init(context, c, FLAG_AUTO_REQUERY);
124 }
125
126 /**
127 * Constructor that allows control over auto-requery. It is recommended
128 * you not use this, but instead {@link #CursorAdapter(Context, Cursor, int)}.
129 * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
130 * will always be set.
131 *
132 * @param c The cursor from which to get the data.
133 * @param context The context
134 * @param autoRequery If true the adapter will call requery() on the
135 * cursor whenever it changes so the most recent
136 * data is always displayed. Using true here is discouraged.
137 */
138 public CursorAdapter(Context context, Cursor c, boolean autoRequery) {
139 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
140 }
141
142 /**
143 * Recommended constructor.
144 *
145 * @param c The cursor from which to get the data.
146 * @param context The context
147 * @param flags Flags used to determine the behavior of the adapter; may
148 * be any combination of {@link #FLAG_AUTO_REQUERY} and
149 * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
150 */
151 public CursorAdapter(Context context, Cursor c, int flags) {
152 init(context, c, flags);
153 }
154
155 /**
156 * @deprecated Don't use this, use the normal constructor. This will
157 * be removed in the future.
158 */
159 @Deprecated
160 protected void init(Context context, Cursor c, boolean autoRequery) {
161 init(context, c, autoRequery ? FLAG_AUTO_REQUERY : FLAG_REGISTER_CONTENT_OBSERVER);
162 }
163
164 void init(Context context, Cursor c, int flags) {
165 if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
166 flags |= FLAG_REGISTER_CONTENT_OBSERVER;
167 mAutoRequery = true;
168 } else {
169 mAutoRequery = false;
170 }
171 boolean cursorPresent = c != null;
172 mCursor = c;
173 mDataValid = cursorPresent;
174 mContext = context;
175 mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
176 if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
177 mChangeObserver = new ChangeObserver();
178 mDataSetObserver = new MyDataSetObserver();
179 } else {
180 mChangeObserver = null;
181 mDataSetObserver = null;
182 }
183
184 if (cursorPresent) {
185 if (mChangeObserver != null) c.registerContentObserver(mChangeObserver);
186 if (mDataSetObserver != null) c.registerDataSetObserver(mDataSetObserver);
187 }
188 }
189
190 /**
191 * Returns the cursor.
192 * @return the cursor.
193 */
194 public Cursor getCursor() {
195 return mCursor;
196 }
197
198 /**
199 * @see android.widget.ListAdapter#getCount()
200 */
201 public int getCount() {
202 if (mDataValid && mCursor != null) {
203 return mCursor.getCount();
204 } else {
205 return 0;
206 }
207 }
208
209 /**
210 * @see android.widget.ListAdapter#getItem(int)
211 */
212 public Object getItem(int position) {
213 if (mDataValid && mCursor != null) {
214 mCursor.moveToPosition(position);
215 return mCursor;
216 } else {
217 return null;
218 }
219 }
220
221 /**
222 * @see android.widget.ListAdapter#getItemId(int)
223 */
224 public long getItemId(int position) {
225 if (mDataValid && mCursor != null) {
226 if (mCursor.moveToPosition(position)) {
227 return mCursor.getLong(mRowIDColumn);
228 } else {
229 return 0;
230 }
231 } else {
232 return 0;
233 }
234 }
235
236 @Override
237 public boolean hasStableIds() {
238 return true;
239 }
240
241 /**
242 * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
243 */
244 public View getView(int position, View convertView, ViewGroup parent) {
245 if (!mDataValid) {
246 throw new IllegalStateException("this should only be called when the cursor is valid");
247 }
248 if (!mCursor.moveToPosition(position)) {
249 throw new IllegalStateException("couldn't move cursor to position " + position);
250 }
251 View v;
252 if (convertView == null) {
253 v = newView(mContext, mCursor, parent);
254 } else {
255 v = convertView;
256 }
257 bindView(v, mContext, mCursor);
258 return v;
259 }
260
261 @Override
262 public View getDropDownView(int position, View convertView, ViewGroup parent) {
263 if (mDataValid) {
264 mCursor.moveToPosition(position);
265 View v;
266 if (convertView == null) {
267 v = newDropDownView(mContext, mCursor, parent);
268 } else {
269 v = convertView;
270 }
271 bindView(v, mContext, mCursor);
272 return v;
273 } else {
274 return null;
275 }
276 }
277
278 /**
279 * Makes a new view to hold the data pointed to by cursor.
280 * @param context Interface to application's global information
281 * @param cursor The cursor from which to get the data. The cursor is already
282 * moved to the correct position.
283 * @param parent The parent to which the new view is attached to
284 * @return the newly created view.
285 */
286 public abstract View newView(Context context, Cursor cursor, ViewGroup parent);
287
288 /**
289 * Makes a new drop down view to hold the data pointed to by cursor.
290 * @param context Interface to application's global information
291 * @param cursor The cursor from which to get the data. The cursor is already
292 * moved to the correct position.
293 * @param parent The parent to which the new view is attached to
294 * @return the newly created view.
295 */
296 public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
297 return newView(context, cursor, parent);
298 }
299
300 /**
301 * Bind an existing view to the data pointed to by cursor
302 * @param view Existing view, returned earlier by newView
303 * @param context Interface to application's global information
304 * @param cursor The cursor from which to get the data. The cursor is already
305 * moved to the correct position.
306 */
307 public abstract void bindView(View view, Context context, Cursor cursor);
308
309 /**
310 * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
311 * closed.
312 *
313 * @param cursor The new cursor to be used
314 */
315 public void changeCursor(Cursor cursor) {
316 Cursor old = swapCursor(cursor);
317 if (old != null) {
318 old.close();
319 }
320 }
321
322 /**
323 * Swap in a new Cursor, returning the old Cursor. Unlike
324 * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
325 * closed.
326 *
327 * @param newCursor The new cursor to be used.
328 * @return Returns the previously set Cursor, or null if there wasa not one.
329 * If the given new Cursor is the same instance is the previously set
330 * Cursor, null is also returned.
331 */
332 public Cursor swapCursor(Cursor newCursor) {
333 if (newCursor == mCursor) {
334 return null;
335 }
336 Cursor oldCursor = mCursor;
337 if (oldCursor != null) {
338 if (mChangeObserver != null) oldCursor.unregisterContentObserver(mChangeObserver);
339 if (mDataSetObserver != null) oldCursor.unregisterDataSetObserver(mDataSetObserver);
340 }
341 mCursor = newCursor;
342 if (newCursor != null) {
343 if (mChangeObserver != null) newCursor.registerContentObserver(mChangeObserver);
344 if (mDataSetObserver != null) newCursor.registerDataSetObserver(mDataSetObserver);
345 mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
346 mDataValid = true;
347 // notify the observers about the new cursor
348 notifyDataSetChanged();
349 } else {
350 mRowIDColumn = -1;
351 mDataValid = false;
352 // notify the observers about the lack of a data set
353 notifyDataSetInvalidated();
354 }
355 return oldCursor;
356 }
357
358 /**
359 * <p>Converts the cursor into a CharSequence. Subclasses should override this
360 * method to convert their results. The default implementation returns an
361 * empty String for null values or the default String representation of
362 * the value.</p>
363 *
364 * @param cursor the cursor to convert to a CharSequence
365 * @return a CharSequence representing the value
366 */
367 public CharSequence convertToString(Cursor cursor) {
368 return cursor == null ? "" : cursor.toString();
369 }
370
371 /**
372 * Runs a query with the specified constraint. This query is requested
373 * by the filter attached to this adapter.
374 *
375 * The query is provided by a
376 * {@link android.widget.FilterQueryProvider}.
377 * If no provider is specified, the current cursor is not filtered and returned.
378 *
379 * After this method returns the resulting cursor is passed to {@link #changeCursor(Cursor)}
380 * and the previous cursor is closed.
381 *
382 * This method is always executed on a background thread, not on the
383 * application's main thread (or UI thread.)
384 *
385 * Contract: when constraint is null or empty, the original results,
386 * prior to any filtering, must be returned.
387 *
388 * @param constraint the constraint with which the query must be filtered
389 *
390 * @return a Cursor representing the results of the new query
391 *
392 * @see #getFilter()
393 * @see #getFilterQueryProvider()
394 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
395 */
396 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
397 if (mFilterQueryProvider != null) {
398 return mFilterQueryProvider.runQuery(constraint);
399 }
400
401 return mCursor;
402 }
403
404 public Filter getFilter() {
405 if (mCursorFilter == null) {
406 mCursorFilter = new CursorFilter(this);
407 }
408 return mCursorFilter;
409 }
410
411 /**
412 * Returns the query filter provider used for filtering. When the
413 * provider is null, no filtering occurs.
414 *
415 * @return the current filter query provider or null if it does not exist
416 *
417 * @see #setFilterQueryProvider(android.widget.FilterQueryProvider)
418 * @see #runQueryOnBackgroundThread(CharSequence)
419 */
420 public FilterQueryProvider getFilterQueryProvider() {
421 return mFilterQueryProvider;
422 }
423
424 /**
425 * Sets the query filter provider used to filter the current Cursor.
426 * The provider's
427 * {@link android.widget.FilterQueryProvider#runQuery(CharSequence)}
428 * method is invoked when filtering is requested by a client of
429 * this adapter.
430 *
431 * @param filterQueryProvider the filter query provider or null to remove it
432 *
433 * @see #getFilterQueryProvider()
434 * @see #runQueryOnBackgroundThread(CharSequence)
435 */
436 public void setFilterQueryProvider(FilterQueryProvider filterQueryProvider) {
437 mFilterQueryProvider = filterQueryProvider;
438 }
439
440 /**
441 * Called when the {@link ContentObserver} on the cursor receives a change notification.
442 * The default implementation provides the auto-requery logic, but may be overridden by
443 * sub classes.
444 *
445 * @see ContentObserver#onChange(boolean)
446 */
447 protected void onContentChanged() {
448 if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
449 if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
450 mDataValid = mCursor.requery();
451 }
452 }
453
454 private class ChangeObserver extends ContentObserver {
455 public ChangeObserver() {
456 super(new Handler());
457 }
458
459 @Override
460 public boolean deliverSelfNotifications() {
461 return true;
462 }
463
464 @Override
465 public void onChange(boolean selfChange) {
466 onContentChanged();
467 }
468 }
469
470 private class MyDataSetObserver extends DataSetObserver {
471 @Override
472 public void onChanged() {
473 mDataValid = true;
474 notifyDataSetChanged();
475 }
476
477 @Override
478 public void onInvalidated() {
479 mDataValid = false;
480 notifyDataSetInvalidated();
481 }
482 }
483
484}