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