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