CursorAdapter.java revision cba2e2c881e8e16ea5025b564c94320174d65f01
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.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} 485